├── .babelrc
├── .dockerignore
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .gitattributes
├── .github
└── FUNDING.yml
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CODING_GUIDELINES.md
├── CONTRIBUTING.md
├── Dockerfile
├── EXAMPLES.md
├── LICENSE
├── NEW_CONNECTION.md
├── NEW_RELEASE.md
├── ORACLE.md
├── Procfile
├── README.md
├── app.json
├── app
├── actions
│ └── sessions.js
├── background.tiff
├── components
│ ├── Configuration.react.js
│ ├── Link.react.js
│ ├── Login.react.js
│ ├── Oauth.react.js
│ ├── Settings
│ │ ├── ConnectButton
│ │ │ └── ConnectButton.react.js
│ │ ├── DialectSelector
│ │ │ └── DialectSelector.react.js
│ │ ├── Logger
│ │ │ ├── Logger.react.js
│ │ │ └── LoggerController.react.js
│ │ ├── OptionsDropdown
│ │ │ ├── OptionsDropdown.react.js
│ │ │ ├── es-docs-options.jsx
│ │ │ ├── es-indices-options.jsx
│ │ │ └── sql-options.jsx
│ │ ├── Preview
│ │ │ ├── ApacheDrillPreview.js
│ │ │ ├── Preview.react.js
│ │ │ ├── S3Preview.js
│ │ │ ├── TableTree.react.js
│ │ │ ├── chart-editor.jsx
│ │ │ ├── code-editor.css
│ │ │ ├── code-editor.jsx
│ │ │ ├── sql-table.css
│ │ │ └── sql-table.jsx
│ │ ├── Settings.react.js
│ │ ├── Tabs
│ │ │ ├── Tab.react.js
│ │ │ └── Tabs.react.js
│ │ ├── UserConnections
│ │ │ ├── UserConnections.react.js
│ │ │ └── filedrop.jsx
│ │ ├── cron-picker
│ │ │ ├── cron-helpers.js
│ │ │ ├── cron-picker.css
│ │ │ ├── cron-picker.jsx
│ │ │ ├── details.jsx
│ │ │ ├── modes
│ │ │ │ ├── daily.jsx
│ │ │ │ ├── frequently.js
│ │ │ │ ├── hourly.jsx
│ │ │ │ ├── index.js
│ │ │ │ ├── minute.js
│ │ │ │ ├── monthly.jsx
│ │ │ │ └── weekly.jsx
│ │ │ └── time-pickers.jsx
│ │ └── scheduler
│ │ │ ├── modals
│ │ │ ├── create-modal
│ │ │ │ ├── create-modal.css
│ │ │ │ └── create-modal.jsx
│ │ │ ├── login-modal
│ │ │ │ ├── login-modal.css
│ │ │ │ └── login-modal.jsx
│ │ │ ├── preview-modal.jsx
│ │ │ └── tags-modal
│ │ │ │ ├── tags-modal.css
│ │ │ │ └── tags-modal.jsx
│ │ │ ├── pickers
│ │ │ ├── color-picker.jsx
│ │ │ └── tag-picker.jsx
│ │ │ ├── presentational
│ │ │ ├── api-call-counts.jsx
│ │ │ ├── execution-details.jsx
│ │ │ ├── request-error.jsx
│ │ │ ├── sql.jsx
│ │ │ ├── status.jsx
│ │ │ ├── tag.jsx
│ │ │ ├── timed-message.jsx
│ │ │ └── timestamp.jsx
│ │ │ ├── scheduler.css
│ │ │ └── scheduler.jsx
│ ├── error.jsx
│ ├── layout.jsx
│ ├── modal.jsx
│ └── success.jsx
├── constants
│ └── constants.js
├── containers
│ └── DevTools.js
├── icons
│ ├── app.icns
│ ├── app.ico
│ ├── app_1024x1024.png
│ ├── app_16x16.png
│ ├── app_256x256.png
│ ├── app_32x32.png
│ └── app_512x512.png
├── images
│ ├── add.png
│ ├── apache_drill-logo.png
│ ├── athena-logo.LICENSE
│ ├── athena-logo.png
│ ├── bigquery-logo.LICENSE
│ ├── bigquery-logo.png
│ ├── checkmark.png
│ ├── clickhouse-logo.png
│ ├── csv-logo.png
│ ├── dataworld-logo.png
│ ├── delete.png
│ ├── elastic-logo.png
│ ├── favicon.ico
│ ├── green-dot.svg
│ ├── ibmdb2-logo.png
│ ├── impala-logo.png
│ ├── import-modal-http.png
│ ├── import-modal-https.png
│ ├── livy-logo.LICENSE
│ ├── livy-logo.png
│ ├── mariadb-logo-small.png
│ ├── mariadb-logo.png
│ ├── mssql-logo-small.png
│ ├── mssql-logo.png
│ ├── mysql-logo-small.png
│ ├── mysql-logo.png
│ ├── oracle-logo.png
│ ├── plotly-connector-logo.svg
│ ├── plotly-logo-no-name.png
│ ├── plotly-logo.png
│ ├── postgres-logo-small.png
│ ├── postgres-logo.png
│ ├── red-dot.svg
│ ├── redshift-logo.png
│ ├── running.png
│ ├── s3-logo.png
│ ├── spark-logo.LICENSE
│ ├── spark-logo.png
│ ├── sqlite-logo-small.png
│ ├── sqlite-logo.png
│ ├── unfold.png
│ ├── x.png
│ └── yellow-dot.svg
├── index.js
├── reducers
│ └── index.js
├── store
│ ├── configureStore.development.js
│ ├── configureStore.js
│ └── configureStore.production.js
├── utils
│ ├── codemirror
│ │ └── clickhouse.js
│ ├── queryUtils.js
│ └── utils.js
└── yarn.lock
├── appveyor.yml
├── backend
├── certificates.js
├── constants.js
├── headless.development.js
├── init.js
├── logger.js
├── main.development.js
├── menus.js
├── parse.js
├── persistent
│ ├── Connections.js
│ ├── Queries.js
│ ├── QueryScheduler.js
│ ├── Tags.js
│ ├── datastores
│ │ ├── ApacheDrill.js
│ │ ├── Datastores.js
│ │ ├── Elasticsearch.js
│ │ ├── S3.js
│ │ ├── athena.js
│ │ ├── bigquery.js
│ │ ├── clickhouse.js
│ │ ├── csv.js
│ │ ├── datastoremock.js
│ │ ├── dataworld.js
│ │ ├── drivers
│ │ │ └── athena.js
│ │ ├── ibmdb2.js
│ │ ├── impala.js
│ │ ├── livy.js
│ │ ├── oracle.js
│ │ ├── pool.js
│ │ └── sql.js
│ └── plotly-api.js
├── plugins
│ └── authorization.js
├── preload.js
├── routes.js
├── settings.js
└── utils
│ ├── authUtils.js
│ ├── cronUtils.js
│ ├── gridUtils.js
│ ├── homeFiles.js
│ └── persistenceUtils.js
├── circle.yml
├── mock-storage
└── connections.yaml
├── package.json
├── sample-storage
└── connections.yaml
├── scripts
├── fix-module-ibmdb.js
└── rebuild-modules.js
├── shared
└── constants.js
├── static
├── browserconfig.xml
├── css-vendor
│ ├── Resizer.css
│ ├── Treeview.css
│ ├── normalize.css
│ ├── react-dd-menu.css
│ ├── react-select-custom.css
│ ├── react-select.min.css
│ ├── react-tabs.css
│ ├── show-hint.css
│ └── skeleton.css
├── css
│ ├── Configuration.css
│ ├── ConnectButton.css
│ ├── DialectSelector.css
│ ├── Link.css
│ ├── Logger.css
│ ├── Preview.css
│ ├── Settings.css
│ ├── TableDropdown.css
│ ├── Tabs.css
│ ├── UserConnections.css
│ ├── app.global.css
│ ├── fixed-data-table-custom.css
│ └── react-select.css
├── fonts
│ ├── dosis-v7-latin-ext_latin-regular.woff2
│ ├── dosis.LICENSE
│ ├── open-sans-v15-vietnamese_greek_latin-ext_greek-ext_cyrillic-ext_cyrillic_latin-regular.woff2
│ ├── open-sans.LICENSE
│ ├── ubuntu-mono-v7-greek_latin-ext_greek-ext_cyrillic-ext_cyrillic_latin-regular.woff2
│ ├── ubuntu-v11-greek_latin-ext_greek-ext_cyrillic-ext_cyrillic_latin-regular.woff2
│ └── ubuntu.LICENCE
├── images
│ ├── android-icon-144x144.png
│ ├── android-icon-192x192.png
│ ├── android-icon-36x36.png
│ ├── android-icon-48x48.png
│ ├── android-icon-72x72.png
│ ├── android-icon-96x96.png
│ ├── apple-icon-114x114.png
│ ├── apple-icon-120x120.png
│ ├── apple-icon-144x144.png
│ ├── apple-icon-152x152.png
│ ├── apple-icon-180x180.png
│ ├── apple-icon-57x57.png
│ ├── apple-icon-60x60.png
│ ├── apple-icon-72x72.png
│ ├── apple-icon-76x76.png
│ ├── apple-icon-precomposed.png
│ ├── apple-icon.png
│ ├── falcon-logo-by-plotly-stripe.png
│ ├── falcon_hero.gif
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── favicon-96x96.png
│ ├── favicon.ico
│ ├── ms-icon-144x144.png
│ ├── ms-icon-150x150.png
│ ├── ms-icon-310x310.png
│ └── ms-icon-70x70.png
├── index.html
└── oauth.html
├── test
├── .eslintrc
├── app
│ ├── components
│ │ └── Settings
│ │ │ ├── ConnectButton
│ │ │ └── ConnectButton.test.js
│ │ │ ├── DialectSelector
│ │ │ └── DialectSelector.test.js
│ │ │ ├── Preview
│ │ │ ├── chart-editor.test.jsx
│ │ │ └── sql-table.test.jsx
│ │ │ ├── UserConnections
│ │ │ └── filedrop.test.jsx
│ │ │ └── scheduler
│ │ │ ├── color-picker.test.jsx
│ │ │ ├── create-modal.test.jsx
│ │ │ ├── login-modal.test.jsx
│ │ │ ├── preview-modal.test.jsx
│ │ │ ├── scheduler.test.jsx
│ │ │ ├── tag-picker.test.jsx
│ │ │ └── tags-modal.test.jsx
│ └── utils.js
├── athena
│ ├── SETUP_ATHENA.md
│ └── athena-sample.csv
├── backend
│ ├── Connections.spec.js
│ ├── Datastores.spec.js
│ ├── Queries.spec.js
│ ├── QueryScheduler.spec.js
│ ├── Tags.spec.js
│ ├── certificates.spec.js
│ ├── datastores.athena.spec.js
│ ├── datastores.clickhouse.spec.js
│ ├── datastores.csv.spec.js
│ ├── datastores.dataworld.spec.js
│ ├── datastores.elasticsearch-v2.spec.js
│ ├── datastores.elasticsearch-v5.spec.js
│ ├── datastores.ibmdb.spec.js
│ ├── datastores.impala.spec.js
│ ├── datastores.livy.spec.js
│ ├── datastores.mock.spec.js
│ ├── datastores.oracle.spec.js
│ ├── parse.spec.js
│ ├── plotly-api.spec.js
│ ├── plotly_datasets.db
│ ├── routes.oauth2.spec.js
│ ├── routes.queries.spec.js
│ ├── routes.spec.js
│ ├── routes.tags.spec.js
│ ├── settings.spec.js
│ └── utils.js
├── docker
│ ├── README.md
│ ├── clickhouse
│ │ ├── Dockerfile
│ │ ├── README.md
│ │ └── setup.sh
│ ├── deployment
│ │ ├── db2-deployment.yaml
│ │ ├── db2-service.yaml
│ │ ├── impala-deployment.yaml
│ │ ├── impala-service.yaml
│ │ ├── spark-deployment.yaml
│ │ └── spark-service.yaml
│ ├── elasticsearch
│ │ ├── Dockerfile-v5
│ │ ├── sample-data.test-type.ndjson
│ │ ├── setup-v5.bash
│ │ └── test-types.elastic-2.4-types.ndjson
│ ├── ibmdb2
│ │ ├── Dockerfile
│ │ ├── README.md
│ │ └── build.sh
│ ├── impala
│ │ ├── Dockerfile
│ │ ├── README.md
│ │ └── bin
│ │ │ ├── setup_database.sh
│ │ │ └── setup_test_db.sql
│ ├── oracle
│ │ ├── Dockerfile
│ │ ├── README.md
│ │ ├── setup.ctl
│ │ ├── setup.sh
│ │ └── setup.sql
│ └── spark
│ │ ├── Dockerfile
│ │ ├── README.md
│ │ ├── bin
│ │ ├── create_hive_tables.py
│ │ ├── livy_python_requirements.txt
│ │ ├── setup_database.sh
│ │ ├── setup_java.sh
│ │ └── setup_livy.sh
│ │ ├── conf
│ │ ├── hive-env.sh
│ │ ├── hive-log4j2.properties
│ │ ├── hive-site.xml
│ │ ├── livy.conf
│ │ └── my.cnf
│ │ ├── create_hive_tables.py
│ │ ├── setup_database.sh
│ │ ├── setup_livy.sh
│ │ └── setup_mysql.sh
└── integration_test.js
├── webpack.config.base.js
├── webpack.config.electron.js
├── webpack.config.headless.js
├── webpack.config.production.js
├── webpack.config.web.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "stage-0", "react", "jest"],
3 | "plugins": ["transform-decorators-legacy", "add-module-exports"],
4 | "env": {
5 | "development": {
6 | "presets": ["react-hmre"]
7 | },
8 | "test": {
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # node-waf configuration
20 | .lock-wscript
21 |
22 | # Compiled binary addons (http://nodejs.org/api/addons.html)
23 | build/Release
24 |
25 | # Dependency directory
26 | node_modules
27 |
28 | # OSX
29 | .DS_Store
30 |
31 | # App packaged
32 | dist
33 | release
34 | main.js
35 | main.js.map
36 | static/*.js*
37 |
38 | # Local tests
39 | test/set_creds.sh
40 |
41 | *.pem
42 |
43 | *.srl
44 |
45 | .srl
46 |
47 | backend/headlessBundle.js
48 |
49 | backend/headlessBundle.js.map
50 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = tab
5 | end_of_line = lf
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
9 |
10 | [*.{json,js,jsx,html,css,yml}]
11 | indent_style = space
12 | indent_size = 4
13 |
14 | [.eslintrc]
15 | indent_style = space
16 | indent_size = 4
17 |
18 | [*.md]
19 | trim_trailing_whitespace = false
20 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | main.js
2 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | custom: https://plot.ly/products/consulting-and-oem/
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | npm-debug.log
2 |
3 | # Tern
4 | .tern-port
5 |
6 | # Vim
7 | *.sw?
8 |
9 | # IntelliJ Idea
10 | .idea
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | sample-storage/log.log
17 | *.log
18 | *.log.*
19 |
20 | # Directory for instrumented libs generated by jscoverage/JSCover
21 | lib-cov
22 |
23 | # Coverage directory used by tools like istanbul
24 | coverage
25 |
26 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
27 | .grunt
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (http://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directory
36 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
37 | node_modules
38 |
39 | # OSX
40 | .DS_Store
41 |
42 | # App packaged
43 | dist
44 | release
45 | main.js
46 | main.js.map
47 | static/*.js*
48 | static/style.css
49 |
50 | # Local tests
51 | test/set_creds.sh
52 | *.pem
53 |
54 | # bundles
55 | backend/headlessBundle.js
56 | backend/headlessBundle.js.map
57 |
58 | *.srl
59 |
60 | .srl
61 |
62 | backend/headlessBundle.js
63 |
64 | backend/headlessBundle.js.map
65 |
66 | npm-shrinkwrap.unsafe.json
67 |
68 | #AWS Credentials
69 | aws.json
70 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | matrix:
2 | include:
3 | - os: osx
4 | osx_image: xcode9.0
5 | language: node_js
6 | node_js: "8"
7 | env:
8 | - ELECTRON_CACHE=$HOME/.cache/electron
9 | - ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder
10 |
11 | cache:
12 | directories:
13 | - node_modules
14 | - $HOME/.cache/electron
15 | - $HOME/.cache/electron-builder
16 | yarn: true
17 |
18 | install:
19 | - yarn install
20 |
21 | script:
22 | - yarn run build
23 | - yarn run pack
24 | - zip --junk-paths mac-falcon-v$(node -p "require('./package.json').version").zip release/*.dmg
25 |
26 | before_cache:
27 | - rm -rf $HOME/.cache/electron-builder/wine
28 |
29 | addons:
30 | artifacts:
31 | s3_region: us-east-1
32 | paths: $(ls mac-falcon-v*.zip | tr "\n" ":")
33 | debug: true
34 |
--------------------------------------------------------------------------------
/CODING_GUIDELINES.md:
--------------------------------------------------------------------------------
1 | # Coding Guidelines
2 |
3 | This is a live document that collects those coding guidelines we intend new code
4 | to follow.
5 |
6 |
7 | ## ESLint
8 |
9 | - Run `yarn lint` to validate your code.
10 |
11 | - Don't commit code with ESLint errors (it'll make the build in CircleCI fail).
12 |
13 | - Code with ESLint warnings won't make the build in CircleCI fail, but reviewers
14 | will likely request they're fixed before merging a contribution.
15 |
16 |
17 | ## Naming Conventions
18 |
19 | ### Path and Filenames
20 |
21 | - Use lower case names and only alphanumeric characters (unless required
22 | otherwise).
23 |
24 | - Avoid stuttering:
25 | - bad: `my-connector/my-connector-driver.js`
26 | - good: `my-connector/driver.js`
27 |
28 | - Use the extension `.jsx` for files that contain JSX and `.js` for those that
29 | only contain JavaScript.
30 |
31 | ### Variable and Function Names
32 |
33 | - Use JavaScript convention, i.e. camelCase.
34 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Usage:
2 | # $ docker build -t chriddyp/database-connector .
3 | # $ PLOTLY_DOMAIN_API="api-local.plot.ly" PLOTLY_STREAMBED_SSL_ENABLED="true" docker run -p 9494:9494 -v ~/.plotly/connector:/home -e PLOTLY_CONNECTOR_PLOTLY_API_DOMAIN=$PLOTLY_DOMAIN_API -e PLOTLY_CONNECTOR_PLOTLY_API_SSL_ENABLED=$PLOTLY_STREAMBED_SSL_ENABLED -e PLOTLY_CONNECTOR_IS_RUNNING_INSIDE_ON_PREM="true" PLOTLY_CONNECTOR_STORAGE_PATH="/home" -d chriddyp/database-connector
4 | #
5 | # Depends on the following ENV variables:
6 | # - PLOTLY_DOMAIN_API (e.g. api-local.plot.ly)
7 | # - PLOTLY_STREAMBED_SSL_ENABLED (e.g. true)
8 | #
9 | # Note that the command line argument PLOTLY_CONNECTOR_IS_RUNNING_INSIDE_ON_PREM="true"
10 | # should only be set if the connector is running on an on prem server.
11 | # If it is running on a local machine but it is targeting a remote on prem server,
12 | # then it should not be set.
13 | #
14 | # Adapted from https://nodejs.org/en/docs/guides/nodejs-docker-webapp/
15 |
16 | FROM node:8
17 |
18 | # I'd like to install the latest version of npm with something like this:
19 | # RUN npm install --global npm@4.0.3
20 | # But unforunately that fails with
21 | # It doesn't seem like there is a good solution in the community yet
22 | # See: https://github.com/npm/npm/issues/9863
23 |
24 | # The App saves folders to the `os.homedir()` directory.
25 | ENV HOME=/home/
26 |
27 | # Create app directory
28 | RUN mkdir -p /usr/src/app
29 | WORKDIR /usr/src/app
30 |
31 | # Create a directory that the app can save files to and that the host can access
32 | # To map to the files that are saved on your host, run with:
33 | # $ docker run -v ~/.plotly/connector:/plotly-connector
34 | # TODO - With that command ^^, it's redudant to call `VOLUME /plotly-connector` right?
35 | # Save that directory as an ENV variable for the app to use when saving files
36 | # By default, the app will save these files in the home directory
37 | ENV PLOTLY_CONNECTOR_DATA_FOLDER="/plotly-connector"
38 |
39 | # Log to stdout in addition to the file
40 | ENV PLOTLY_CONNECTOR_LOG_TO_STDOUT="true"
41 |
42 | # Install app dependencies
43 | COPY package.json /usr/src/app
44 | COPY yarn.lock /usr/src/app
45 | RUN yarn install
46 |
47 | COPY . /usr/src/app
48 | RUN yarn run heroku-postbuild
49 |
50 | ENV PLOTLY_CONNECTOR_PORT 9494
51 | EXPOSE 9494
52 | ENTRYPOINT yarn run start-headless
53 |
--------------------------------------------------------------------------------
/EXAMPLES.md:
--------------------------------------------------------------------------------
1 | ### PostGIS
2 | - [dc counties](https://github.com/alexandresobolevski/plotly_examples/tree/master/postgis/dc)
3 | - [montreal places](https://github.com/alexandresobolevski/plotly_examples/tree/master/postgis/montreal)
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016-2018 Plotly, Inc
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/NEW_RELEASE.md:
--------------------------------------------------------------------------------
1 | This document lists conventions and steps to follow for creating a release.
2 |
3 | # Version Number
4 |
5 | - Patch version numbers are reserved for OnPrem releases. For example, version
6 | `2.4.3` corresponds to an OnPrem release based on Falcon `v2.4.0`.
7 |
8 | # Release Checklist
9 |
10 | - [ ] Create branch `vM.m.p`
11 | - [ ] Update `CHANGELOG.md`
12 | - [ ] Update version number in `package.json`
13 | - [ ] Confirm CircleCI build works
14 | - [ ] Confirm TravisCI build works
15 | - [ ] Confirm AppVeyor build works
16 | - [ ] Confirm Quay build works
17 | - [ ] Confirm Windows installer works and Falcon is able to connect to DB2 and sqlite
18 | - [ ] Confirm Mac installer works and Falcon is able to connect to DB2 and sqlite
19 | - [ ] Do not use Firefox to download the Linux installer from CircleCI ([issue #471](https://github.com/plotly/falcon-sql-client/issues/471))
20 | - [ ] Confirm Linux installer works and Falcon is able to connect to DB2 and sqlite
21 | - [ ] Ensure `yarn build` has been run before manual build
22 | - [ ] Build deb installer manually
23 | - [ ] Confirm deb installer works and Falcon is able to connect to sqlite
24 | - [ ] Build AppImage installer manually
25 | - [ ] Build rpm installer manually
26 | - [ ] Rebase and merge into `master`
27 | - [ ] Tag current `master` as `vM.m.p` and annotate it with the changelog message (this should automatically update the latest release in github)
28 | - [ ] Edit the release to attach all the installers.
29 | - [ ] Check links in https://plot.ly/free-sql-client-download/
30 |
31 | ## Additional Checklist for an OnPrem Release
32 |
33 | - [ ] Create branch `M.m-onprem`
34 | - [ ] Create Quay tag `M.m-onprem` (Visit https://quay.io/repository/plotly/falcon-sql-client?tab=tags , click the gear icon next to the thing you want to tag, then choose “Add new…")
35 |
36 | ## Additional Checklist for an OnPrem Update
37 |
38 | - [ ] Create Quay tag `M.m.p-onprem` (Visit https://quay.io/repository/plotly/falcon-sql-client?tab=tags , click the gear icon next to the thing you want to tag, then choose “Add new…")
39 |
40 |
--------------------------------------------------------------------------------
/ORACLE.md:
--------------------------------------------------------------------------------
1 | # Installation of the free Oracle Install Client
2 |
3 | Unlike the other connectors and as of this writing, the Oracle bindings for
4 | Node.js, [oracledb](https://www.npmjs.com/package/oracledb), do not include the
5 | necessary Oracle Client libraries and users are required to create an account on
6 | [Oracle](https://login.oracle.com/mysso/signon.jsp) before downloading them.
7 |
8 | Although the installation procedure is very well documented
9 | [here](https://github.com/oracle/node-oracledb/blob/master/INSTALL.md#instructions),
10 | see below a quick guide for Windows, Mac and Ubuntu.
11 |
12 |
13 | ## Windows
14 |
15 | *(instructions taken from [oracledb](https://github.com/oracle/node-oracledb/blob/master/INSTALL.md#364-install-the-free-oracle-instant-client-zip))*
16 |
17 | 1. Download the free 64-bit Instant Client Basic ZIP file from [Oracle Technology Network](http://www.oracle.com/technetwork/topics/winx64soft-089540.html).
18 | 2. Extract `instantclient-basic-windows.x64-12.2.0.1.0.zip` to a folder, such as `C:\oracle\instantclient_12_2`.
19 | 3. Add this folder to `PATH`. For example on Windows 7, update `PATH` in `Control Panel -> System -> Advanced System Settings -> Advanced -> Environment Variables -> System variables -> PATH` and add your path, such as `C:\oracle\instantclient_12_2`.
20 | 4. Download and install the correct Visual Studio Redistributable from Microsoft. Instant Client 12.2 requires the [Visual Studio 2013 redistributable](https://support.microsoft.com/en-us/kb/2977003#bookmark-vs2013).
21 |
22 |
23 | ## Mac
24 |
25 | *(instructions taken from [oracledb](https://github.com/oracle/node-oracledb/blob/master/INSTALL.md#354-install-the-free-oracle-instant-client-basic-zip-file))*
26 |
27 | Download the free Basic 64-bit ZIP from [Oracle Technology Network](http://www.oracle.com/technetwork/topics/intel-macsoft-096467.html) and unzip it, for example:
28 |
29 | ```
30 | mkdir -p /opt/oracle
31 | unzip instantclient-basic-macos.x64-12.2.0.1.0.zip
32 | ```
33 |
34 | Create a symbolic link for the 'client shared library' in the user default library path such as in `~/lib` or `/usr/local/lib`. For example:
35 |
36 | ```
37 | mkdir ~/lib
38 | ln -s instantclient_12_2/libclntsh.dylib.12.1 ~/lib/
39 | ```
40 |
41 | Alternatively, copy the required OCI libraries, for example:
42 |
43 | ```
44 | mkdir ~/lib
45 | cp instantclient_12_2/{libclntsh.dylib.12.1,libclntshcore.dylib.12.1,libons.dylib,libnnz12.dylib,libociei.dylib} ~/lib/
46 | ```
47 |
48 |
49 | ## Ubuntu
50 |
51 | 1. Install requirements: `sudo apt-get -qq update && sudo apt-get --no-install-recommends -qq install alien bc libaio1`
52 | 2. Create an account on [Oracle](https://login.oracle.com/mysso/signon.jsp)
53 | 3. Download the Oracle Instant Client from [here](http://download.oracle.com/otn/linux/oracle11g/xe/oracle-xe-11.2.0-1.0.x86_64.rpm.zip)
54 | 4. Unzip `rpm` package: `unzip oracle-xe-11.2.0-1.0.x86_64.rpm.zip`
55 | 5. Convert `rpm` package into `deb`: `alien oracle-xe-11.2.0-1.0.x86_64.rpm`
56 | 6. Install `deb` package: `sudo dpkg -i oracle-instantclient12.2-basiclite_12.2.0.1.0-2_amd64.deb`
57 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: PLOTLY_CONNECTOR_PORT=$PORT PLOTLY_CONNECTOR_STORAGE_PATH="/app/mock-storage" PLOTLY_CONNECTOR_AUTH_ENABLED=false node ./dist/headless-bundle.js
2 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "plotly-database-connector",
3 | "scripts": {
4 | },
5 | "env": {
6 | "PLOTLY_CONNECTOR_STORAGE_PATH": {
7 | "required": true
8 | }
9 | },
10 | "formation": {
11 | "web": {
12 | "quantity": 1
13 | }
14 | },
15 | "addons": [
16 | "papertrail"
17 | ],
18 | "buildpacks": [
19 | {
20 | "url": "heroku/nodejs"
21 | }
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/app/background.tiff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/background.tiff
--------------------------------------------------------------------------------
/app/components/Configuration.react.js:
--------------------------------------------------------------------------------
1 | import cookie from 'react-cookies';
2 | import React, { Component, PropTypes } from 'react';
3 | import {bindActionCreators} from 'redux';
4 | import {connect} from 'react-redux';
5 |
6 | import Login from './Login.react.js';
7 | import * as SessionsActions from '../actions/sessions.js';
8 | import Settings from './Settings/Settings.react.js';
9 | import {isElectron, setUsernameListener} from '../utils/utils.js';
10 |
11 |
12 | class Configuration extends Component {
13 | constructor(props) {
14 | super(props);
15 | this.state = {
16 | authEnabled: (cookie.load('db-connector-auth-enabled') === 'true'),
17 | clientId: cookie.load('db-connector-oauth2-client-id'),
18 | isMenuOpen: false,
19 | username: cookie.load('db-connector-user')
20 | };
21 | this.toggle = this.toggle.bind(this);
22 | this.close = this.close.bind(this);
23 | this.logOut = this.logOut.bind(this);
24 |
25 | /*
26 | * If this is an electron app, then we don't have access to cookies and
27 | * we don't require authentication in the API.
28 | * In the browser, the username is set with a cookie but in electron
29 | * this is set using electron's ipcRenderer.
30 | */
31 | setUsernameListener((event, message) => {
32 | this.setState({username: message});}
33 | );
34 | }
35 |
36 | toggle() {
37 | this.setState({ isMenuOpen: !this.state.isMenuOpen });
38 | }
39 |
40 | close() {
41 | this.setState({ isMenuOpen: false });
42 | }
43 |
44 | logOut() {
45 | /*
46 | * Delete all the cookies and reset user state. This does not kill
47 | * any running connections, but user will not be able to access them
48 | * without logging in again.
49 | */
50 | cookie.remove('db-connector-user');
51 | cookie.remove('plotly-auth-token');
52 | cookie.remove('db-connector-auth-token');
53 | this.setState({ username: ''});
54 |
55 | // reload page when running in browser:
56 | if (!isElectron()) {
57 | window.location.reload();
58 | }
59 | }
60 |
61 | render() {
62 | return (isElectron() || !this.state.authEnabled || this.state.username) ? (
63 |
64 |
65 |
66 | ) : (
67 |
68 |
69 |
70 | );
71 | }
72 | }
73 |
74 | Configuration.propTypes = {
75 | sessionsActions: PropTypes.object
76 | };
77 |
78 | function mapStateToProps(state) {
79 | return {sessions: state.sessions};
80 | }
81 |
82 | function mapDispatchToProps(dispatch) {
83 | const sessionsActions = bindActionCreators(SessionsActions, dispatch);
84 | return {sessionsActions};
85 | }
86 |
87 | export default connect(mapStateToProps, mapDispatchToProps)(Configuration);
88 |
--------------------------------------------------------------------------------
/app/components/Link.react.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export function Link(props) {
4 | return ;
5 | }
6 |
--------------------------------------------------------------------------------
/app/components/Settings/Logger/Logger.react.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ImmutablePropTypes from 'react-immutable-proptypes';
3 | import classnames from 'classnames';
4 |
5 | const Logger = props => {
6 | if (!props.logs || props.logs.length === 0) return null;
7 |
8 | const renderLogs = props.logs.map(log => (
9 | {log.timestamp} - {log.logEntry.toString()}
10 | ));
11 |
12 | const testClass = () => {
13 | /*
14 | Return the number of logs to easily track updates when testing
15 | */
16 | return `test-${props.logs.length}-entries`;
17 | };
18 |
19 | return (
20 |
24 |
Logs
25 |
26 | {renderLogs}
27 |
28 |
29 | );
30 | };
31 |
32 | Logger.propTypes = {
33 | logs: ImmutablePropTypes.map.isRequired
34 | };
35 |
36 | export default Logger;
37 |
--------------------------------------------------------------------------------
/app/components/Settings/Logger/LoggerController.react.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import ImmutablePropTypes from 'react-immutable-proptypes';
3 | import Logger from './Logger.react';
4 |
5 | export default class LoggerController extends Component {
6 | constructor(props) {
7 | super(props);
8 | this.state = {logs: []};
9 | }
10 |
11 | componentWillReceiveProps(nextProps) {
12 |
13 | if (nextProps.ipc.has('log') &&
14 | nextProps.ipc.get('log') !== this.props.ipc.get('log')) {
15 | this.state.logs.unshift(nextProps.ipc.get('log').toJS());
16 | this.setState({logs: this.state.logs});
17 | }
18 |
19 | const errorPath = ['error', 'message'];
20 | if (nextProps.ipc.hasIn(errorPath) &&
21 | nextProps.ipc.getIn(errorPath) !==
22 | this.props.ipc.getIn(errorPath)) {
23 |
24 | this.state.logs.unshift({
25 | logEntry: nextProps.ipc.getIn(errorPath),
26 | timestamp: nextProps.ipc.getIn(['error', 'timestamp'])
27 | });
28 |
29 | this.setState({logs: this.state.logs});
30 | }
31 | }
32 |
33 | render() {
34 | return ;
35 | }
36 | }
37 |
38 | LoggerController.propTypes = {
39 | ipc: ImmutablePropTypes.map.isRequired
40 | };
41 |
--------------------------------------------------------------------------------
/app/components/Settings/OptionsDropdown/OptionsDropdown.react.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import PropTypes from 'prop-types';
3 | import SQLOptions from './sql-options';
4 | import ESIndicesOptions from './es-indices-options';
5 | import ESDocsOptions from './es-docs-options';
6 |
7 | export default class OptionsDropdown extends Component {
8 |
9 | static propTypes = {
10 | // See type of react-select's Select `value` property
11 | selectedTable: PropTypes.string,
12 | selectedIndex: PropTypes.string,
13 |
14 | tablesRequest: PropTypes.object,
15 | setTable: PropTypes.func,
16 | elasticsearchMappingsRequest: PropTypes.object,
17 | setIndex: PropTypes.func
18 | }
19 |
20 | /**
21 | * Options Dropdown is an options drop down
22 | * @param {object} props - Component Properties
23 | * @param {object} props.selectedTable - The selected table
24 | * @param {object} props.tablesRequest - The Requested Table
25 | * @param {function} props.setTable - The set table function
26 | * @param {object} props.elasticsearchMappingsRequest - The ES Mapping Request
27 | * @param {object} props.selectedTable - The selected table
28 | * @param {object} props.selectedIndex - The Selected Index
29 | * @param {function} props.setIndex - The Set the index
30 | */
31 | constructor(props) {
32 | super(props);
33 | }
34 |
35 | render() {
36 | const {
37 | selectedTable,
38 | tablesRequest,
39 | setTable,
40 | elasticsearchMappingsRequest: EMR,
41 | setIndex,
42 | selectedIndex
43 | } = this.props;
44 | return (
45 |
46 |
50 |
54 |
59 |
60 | );
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/app/components/Settings/OptionsDropdown/es-docs-options.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import PropTypes from 'prop-types';
3 | import {keys} from 'ramda';
4 | import Select from 'react-select';
5 |
6 | export default class ESDocsOptions extends Component {
7 | static propTypes = {
8 | selectedTable: PropTypes.string,
9 | selectedIndex: PropTypes.string,
10 | setTable: PropTypes.func,
11 | elasticsearchMappingsRequest: PropTypes.object
12 | }
13 |
14 | /**
15 | * ES Docs Options is an options drop down
16 | * @param {object} props - Component Properties
17 | * @param {object} props.elasticsearchMappingsRequest - The ES Mapping Request
18 | * @param {object} props.selectedTable - The selected table
19 | * @param {object} props.selectedIndex - The Selected Index
20 | * @param {object} props.setTable - The table
21 | */
22 | constructor(props) {
23 | super(props);
24 | }
25 |
26 | render() {
27 | const {
28 | selectedTable,
29 | selectedIndex,
30 | elasticsearchMappingsRequest: EMR,
31 | setTable
32 | } = this.props;
33 |
34 | if (!selectedIndex) {
35 | return null;
36 | }
37 |
38 | const tablesList = keys(EMR.content[selectedIndex].mappings);
39 | if (tablesList.length === 0) {
40 | return {'No docs found'}
;
41 | }
42 |
43 | return (
44 |
47 | ({label: t, value: t}))}
49 | value={selectedTable}
50 | searchable={false}
51 | onChange={option => {
52 | setTable(option.value);
53 | }}
54 | />
55 |
56 | );
57 | }
58 | }
--------------------------------------------------------------------------------
/app/components/Settings/OptionsDropdown/es-indices-options.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import PropTypes from 'prop-types';
3 | import {keys} from 'ramda';
4 | import Select from 'react-select';
5 | import {COLORS} from '../../../constants/constants';
6 |
7 | export default class ESIndicesOptions extends Component {
8 | static propTypes = {
9 | elasticsearchMappingsRequest: PropTypes.object,
10 | setIndex: PropTypes.func,
11 | selectedIndex: PropTypes.string
12 | };
13 |
14 | /**
15 | * ES Indicies Options is an options drop down
16 | * @param {object} props - Component Properties
17 | * @param {object} props.elasticsearchMappingsRequest - The ES Mapping Request
18 | * @param {object} props.setIndex - The Set Index Function
19 | * @param {object} props.selectedIndex - The Selected Index
20 | */
21 | constructor(props) {
22 | super(props);
23 | }
24 |
25 | render() {
26 | const {
27 | elasticsearchMappingsRequest: EMR,
28 | setIndex,
29 | selectedIndex
30 | } = this.props;
31 | if (!EMR.status) {
32 | return null;
33 | } else if (EMR.status === 'loading') {
34 | return {'Loading docs'}
;
35 | } else if (EMR.status > 300) {
36 | // TODO - Make this prettier.
37 | return (
38 |
39 |
{'There was an error loading up your docs'}
40 |
{JSON.stringify(EMR)}
41 |
42 | );
43 | } else if (EMR.status === 200) {
44 | const indeciesList = keys(EMR.content);
45 | if (indeciesList.length === 0) {
46 | return {'No docs found'}
;
47 | }
48 | return (
49 |
52 | ({label: t, value: t}))}
54 | value={selectedIndex}
55 | searchable={false}
56 | onChange={option => {
57 | setIndex(option.value);
58 | }}
59 | />
60 |
61 | );
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/app/components/Settings/OptionsDropdown/sql-options.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import PropTypes from 'prop-types';
3 | import {flatten} from 'ramda';
4 | import Select from 'react-select';
5 | import {COLORS} from '../../../constants/constants';
6 |
7 | export default class SQLOptions extends Component {
8 |
9 | static propTypes = {
10 | selectedTable: PropTypes.string,
11 | tablesRequest: PropTypes.object,
12 | setTable: PropTypes.func
13 | };
14 |
15 | /**
16 | * SQLOptions is an options drop down down
17 | * @param {object} props - Component Properties
18 | * @param {object} props.selectedTable - The selected table
19 | * @param {object} props.tablesRequest - The Requested Table
20 | * @param {function} props.setTable - The set table function
21 | */
22 | constructor(props) {
23 | super(props);
24 | }
25 |
26 | render() {
27 | const {selectedTable, tablesRequest, setTable} = this.props;
28 | if (!tablesRequest.status) {
29 | return null;
30 | } else if (tablesRequest.status === 'loading') {
31 | return {'Loading tables'}
;
32 | } else if (tablesRequest.status > 300) {
33 | // TODO - Make this prettier.
34 | return (
35 |
36 |
{'Hm.. there was an error loading up your tables'}
37 |
{JSON.stringify(tablesRequest)}
38 |
39 | );
40 | } else if (tablesRequest.status === 200) {
41 | const tablesList = flatten(tablesRequest.content);
42 | if (tablesList.length === 0) {
43 | return {'No tables found'}
;
44 | }
45 | return (
46 |
49 | ({label: t, value: t}))}
51 | value={selectedTable}
52 | searchable={false}
53 | onChange={option => {
54 | setTable(option.value);
55 | }}
56 | />
57 |
58 | );
59 | }
60 | }
61 | }
--------------------------------------------------------------------------------
/app/components/Settings/Preview/ApacheDrillPreview.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import PropTypes from 'prop-types';
4 |
5 | const ApacheDrillPreview = function(props) {
6 | const {
7 | apacheDrillStorageRequest,
8 | apacheDrillS3KeysRequest
9 | } = props;
10 |
11 | if (apacheDrillStorageRequest.status >= 400) {
12 | return ({'Hm... An error while trying to load Apache Drill'}
);
13 | } else if (apacheDrillStorageRequest.status === 'loading') {
14 | return ({'Loading...'}
);
15 | } else if (apacheDrillStorageRequest.status === 200) {
16 | const storage = (
17 |
18 |
Enabled Apache Drill Storage Plugins
19 |
20 | {apacheDrillStorageRequest.content
21 | .filter(object => object.config.enabled)
22 | .map(object => (
23 |
{`${object.name} - ${object.config.connection}`}
24 | ))
25 | }
26 |
27 |
28 | );
29 |
30 | let availableFiles = null;
31 | if (apacheDrillS3KeysRequest.status === 200) {
32 | const parquetFiles = apacheDrillS3KeysRequest
33 | .content
34 | .filter(object => object.Key.indexOf('.parquet') > -1)
35 | .map(object => object.Key.slice(0, object.Key.indexOf('.parquet')) + '.parquet');
36 | const uniqueParquetFiles = [];
37 | parquetFiles.forEach(file => {
38 | if (uniqueParquetFiles.indexOf(file) === -1) {
39 | uniqueParquetFiles.push(file);
40 | }
41 | });
42 | if (uniqueParquetFiles.length === 0) {
43 | availableFiles = (
44 |
45 | Heads up! It looks like no parquet files were
46 | found in this S3 bucket.
47 |
48 | );
49 | } else {
50 | availableFiles = (
51 |
52 |
Available Parquet Files on S3
53 |
54 | {uniqueParquetFiles.map(key => (
55 |
{`${key}`}
56 | ))}
57 |
58 |
59 | );
60 | }
61 | }
62 | return (
63 |
64 | {storage}
65 | {availableFiles}
66 |
67 | );
68 | }
69 |
70 | return null;
71 | };
72 |
73 | ApacheDrillPreview.propTypes = {
74 | apacheDrillStorageRequest: PropTypes.object,
75 | apacheDrillS3KeysRequest: PropTypes.object
76 | };
77 |
78 | export default ApacheDrillPreview;
79 |
--------------------------------------------------------------------------------
/app/components/Settings/Preview/S3Preview.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const S3Preview = function(props) {
5 | const {s3KeysRequest} = props;
6 | if (s3KeysRequest.status >= 400) {
7 | return ({'Hm... An error occurred while trying to load S3 keys'}
);
8 | } else if (s3KeysRequest.status === 'loading') {
9 | return ({'Loading...'}
);
10 | } else if (s3KeysRequest.status === 200) {
11 | return (
12 |
13 |
CSV Files on S3
14 |
15 | {s3KeysRequest.content.filter(object => object.Key.endsWith('.csv'))
16 | .map(object =>
{object.Key}
17 | )}
18 |
19 |
20 | );
21 | }
22 |
23 | return null;
24 | };
25 |
26 | S3Preview.propTypes = {
27 | s3KeysRequest: PropTypes.object
28 | };
29 |
30 | export default S3Preview;
31 |
--------------------------------------------------------------------------------
/app/components/Settings/Preview/code-editor.css:
--------------------------------------------------------------------------------
1 | .CodeMirror {
2 | border: 1px solid #c8d4e3;
3 | font-family: 'Ubuntu Mono', monospace;
4 | font-size: 14px;
5 | height: 140px;
6 | margin-bottom: 20px;
7 | width: 740px;
8 | }
9 |
10 | .CodeMirror-gutters {
11 | background-color: #f3f6fa;
12 | border-right: 1px solid #c8d4e3;
13 | }
14 |
15 | .react-resizable-handle {
16 | z-index: 9;
17 | }
18 |
19 | .scheduleButton {
20 | bottom: 18px;
21 | position: absolute;
22 | right: 136px;
23 | z-index: 10;
24 | }
25 |
--------------------------------------------------------------------------------
/app/components/Settings/Preview/sql-table.css:
--------------------------------------------------------------------------------
1 | .react-grid-Main {
2 | font-size: 12px;
3 | outline: 0 !important;
4 | }
5 |
6 | .react-grid-Grid, .react-grid-Header, .react-grid-HeaderCell {
7 | background: #f3f6fa !important;
8 | }
9 |
10 | .react-grid-Grid {
11 | border-color: #c8d4e3 !important;
12 | }
13 |
14 | /* fix horizontal overflow issues */
15 | .react-grid-Cell {
16 | box-sizing: border-box;
17 | border-right: none !important;
18 | }
19 |
20 |
21 | .react-grid-Header {
22 | box-shadow: unset !important;
23 | max-height: 64px;
24 | overflow: hidden;
25 | }
26 |
27 | .react-grid-HeaderRow {
28 | max-height: 32px;
29 | overflow: hidden;
30 | }
31 |
32 | .react-grid-HeaderCell .form-control {
33 | font-family: inherit;
34 | font-size: inherit;
35 | font-weight: normal;
36 | margin: inherit;
37 | padding: 0;
38 | width: 100%;
39 | }
40 |
41 | .react-grid-Viewport {
42 | border-top: 1px solid #c8d4e3;
43 | }
44 |
45 | .react-grid-Row--odd .react-grid-Cell {
46 | background: #fafbfd;
47 | }
48 |
49 | .react-grid-Cell:focus {
50 | outline: 0 !important;
51 | }
52 |
--------------------------------------------------------------------------------
/app/components/Settings/Tabs/Tabs.react.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import PropTypes from 'prop-types';
3 | import classnames from 'classnames';
4 | import {keys} from 'ramda';
5 | import Tab from './Tab.react';
6 |
7 | export default class ConnectionTabs extends Component {
8 | constructor(props) {
9 | super(props);
10 | }
11 |
12 | static propTypes = {
13 | connections: PropTypes.object,
14 | selectedTab: PropTypes.string,
15 | newTab: PropTypes.func,
16 | setTab: PropTypes.func,
17 | deleteTab: PropTypes.func
18 | }
19 |
20 | render() {
21 | const {connections, selectedTab, newTab, setTab, deleteTab} = this.props;
22 | return (
23 |
24 |
25 | {keys(connections).map(tabId =>
26 |
1}
34 | />
35 | )}
36 |
37 |
38 |
44 |
45 |
46 |
47 |
48 | );
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/app/components/Settings/cron-picker/cron-picker.css:
--------------------------------------------------------------------------------
1 | .time-picker .Select-control {
2 | /* all other time related dropdowns */
3 | width: 56px;
4 | }
5 |
6 | .cron-row {
7 | vertical-align: center;
8 | align-items: center;
9 | margin-bottom: 16px;
10 | }
11 |
12 | .cron-details {
13 | align-items: center;
14 | min-height: 56px;
15 | background: #E8E8E8;
16 | border-left: 4px solid #828282;
17 | padding: 8px 16px;
18 | margin-bottom: 16px;
19 | }
20 |
--------------------------------------------------------------------------------
/app/components/Settings/cron-picker/details.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import {Column, Row} from '../../layout.jsx';
5 |
6 | const style = {justifyContent: 'flex-start'};
7 |
8 | const DetailsRow = props => (
9 |
10 | {props.children}
11 |
12 | );
13 |
14 | DetailsRow.propTypes = {
15 | children: PropTypes.node
16 | };
17 |
18 | const DetailsColumn = props => (
19 |
20 | {props.children}
21 |
22 | );
23 |
24 | DetailsColumn.propTypes = {
25 | children: PropTypes.node
26 | };
27 |
28 | export default {DetailsRow, DetailsColumn};
29 |
--------------------------------------------------------------------------------
/app/components/Settings/cron-picker/modes/daily.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import {HourInput, MinuteInput, AmPmInput} from '../time-pickers.jsx';
5 |
6 | import {DetailsRow} from '../details.jsx';
7 | import {mapHourToCronFormat} from '../cron-helpers.js';
8 |
9 | const id = 'DAILY';
10 | const name = 'Run every day';
11 |
12 | class component extends React.Component {
13 | static defaultProps = {
14 | initialTime: {
15 | hour: 12,
16 | minute: 0,
17 | amPm: 'AM'
18 | }
19 | };
20 |
21 | static propTypes = {
22 | initialTime: PropTypes.object,
23 | onChange: PropTypes.func.isRequired
24 | };
25 |
26 | constructor(props) {
27 | super(props);
28 |
29 | this.state = {
30 | time: props.initialTime
31 | };
32 |
33 | props.onChange(this.toCronExpression(props.initialTime));
34 |
35 | this.onChange = this.onChange.bind(this);
36 | }
37 |
38 | toCronExpression({hour, minute, amPm}) {
39 | // adjust hour to fit cron format if needed
40 | const normalizedHour = mapHourToCronFormat(hour, amPm);
41 | return `${minute} ${normalizedHour} * * *`;
42 | }
43 |
44 | onChange(key, selectedOption) {
45 | const newValue = selectedOption.value;
46 | const time = this.state.time;
47 |
48 | time[key] = newValue;
49 |
50 | this.setState({time});
51 | this.props.onChange(this.toCronExpression(time));
52 | }
53 |
54 | render() {
55 | const hourInput = (
56 |
61 | );
62 | const minuteInput = (
63 |
68 | );
69 | const amPmInput = (
70 |
75 | );
76 |
77 | return (
78 |
79 | at
80 | {hourInput}:{minuteInput} {amPmInput}
81 |
82 | );
83 | }
84 | }
85 |
86 | export default {
87 | id,
88 | name,
89 | component
90 | };
91 |
--------------------------------------------------------------------------------
/app/components/Settings/cron-picker/modes/frequently.js:
--------------------------------------------------------------------------------
1 | const id = 'FREQUENTLY';
2 | const name = 'Run every 5 minutes';
3 |
4 | const staticCronExpression = '*/5 * * * *';
5 |
6 | export default {
7 | id,
8 | name,
9 | staticCronExpression,
10 | component: null
11 | };
12 |
--------------------------------------------------------------------------------
/app/components/Settings/cron-picker/modes/hourly.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import {MinuteInput} from '../time-pickers.jsx';
5 |
6 | import {DetailsRow} from '../details.jsx';
7 |
8 | const id = 'HOURLY';
9 | const name = 'Run every hour';
10 |
11 | class component extends React.Component {
12 | static defaultProps = {
13 | initialTime: {
14 | minute: 0
15 | }
16 | };
17 |
18 | static propTypes = {
19 | initialTime: PropTypes.object,
20 | onChange: PropTypes.func.isRequired
21 | };
22 |
23 | constructor(props) {
24 | super(props);
25 |
26 | this.state = {
27 | time: props.initialTime
28 | };
29 |
30 | props.onChange(this.toCronExpression(props.initialTime.minute));
31 |
32 | this.onChange = this.onChange.bind(this);
33 | }
34 |
35 | toCronExpression(minute) {
36 | return `${minute} * * * *`;
37 | }
38 |
39 | onChange(minuteOption) {
40 | const newMinute = minuteOption.value;
41 | this.setState({time: {minute: newMinute}});
42 | this.props.onChange(this.toCronExpression(newMinute));
43 | }
44 |
45 | render() {
46 | return (
47 |
48 | at minute
49 |
50 |
51 | );
52 | }
53 | }
54 |
55 | export default {
56 | id,
57 | name,
58 | component
59 | };
60 |
--------------------------------------------------------------------------------
/app/components/Settings/cron-picker/modes/index.js:
--------------------------------------------------------------------------------
1 | import Minute from './minute.js';
2 | import Frequently from './frequently.js';
3 | import Hourly from './hourly.jsx';
4 | import Daily from './daily.jsx';
5 | import Weekly from './weekly.jsx';
6 | import Monthly from './monthly.jsx';
7 |
8 | export default [Minute, Frequently, Hourly, Daily, Weekly, Monthly];
9 |
--------------------------------------------------------------------------------
/app/components/Settings/cron-picker/modes/minute.js:
--------------------------------------------------------------------------------
1 | const id = 'MINUTE';
2 | const name = 'Run every minute';
3 |
4 | const staticCronExpression = '* * * * *';
5 |
6 | export default {
7 | id,
8 | name,
9 | staticCronExpression,
10 | component: null
11 | };
12 |
--------------------------------------------------------------------------------
/app/components/Settings/cron-picker/modes/monthly.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import {DateInput, HourInput, MinuteInput, AmPmInput} from '../time-pickers.jsx';
5 | import {mapHourToCronFormat} from '../cron-helpers.js';
6 | import {DetailsRow} from '../details.jsx';
7 |
8 | const id = 'MONTHLY';
9 | const name = 'Run every month';
10 |
11 | class component extends React.Component {
12 | static defaultProps = {
13 | initialTime: {
14 | date: 1,
15 | hour: 12,
16 | minute: 0,
17 | amPm: 'AM'
18 | }
19 | };
20 |
21 | static propTypes = {
22 | initialTime: PropTypes.object,
23 | onChange: PropTypes.func.isRequired
24 | };
25 |
26 | constructor(props) {
27 | super(props);
28 |
29 | this.state = {
30 | time: props.initialTime
31 | };
32 |
33 | props.onChange(this.toCronExpression(props.initialTime));
34 |
35 | this.onChange = this.onChange.bind(this);
36 | }
37 |
38 | toCronExpression({date, hour, minute, amPm}) {
39 | // adjust hour to fit cron format if needed
40 | const normalizedHour = mapHourToCronFormat(hour, amPm);
41 |
42 | return `${minute} ${normalizedHour} ${date} * *`;
43 | }
44 |
45 | onChange(key, selectedOption) {
46 | const newValue = selectedOption.value;
47 | const time = this.state.time;
48 |
49 | time[key] = newValue;
50 |
51 | this.setState({time});
52 | this.props.onChange(this.toCronExpression(time));
53 | }
54 |
55 | render() {
56 | const dateInput = (
57 |
62 | );
63 | const hourInput = (
64 |
69 | );
70 | const minuteInput = (
71 |
76 | );
77 | const amPmInput = (
78 |
79 | );
80 |
81 | return (
82 |
83 | on day
84 | {dateInput} at {hourInput}:{minuteInput} {amPmInput}
85 |
86 | );
87 | }
88 | }
89 |
90 | export default {
91 | id,
92 | name,
93 | component
94 | };
95 |
--------------------------------------------------------------------------------
/app/components/Settings/cron-picker/time-pickers.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import {range} from 'ramda';
4 |
5 | import Select from 'react-select';
6 |
7 | // base input params for Select component
8 | const baseParams = {
9 | searchable: false,
10 | clearable: false
11 | };
12 |
13 | function formatOptions(optionsList) {
14 | return optionsList.map(option => {
15 | if (typeof option === 'number') return {label: option, value: option};
16 |
17 | return option;
18 | });
19 | }
20 |
21 | const HOUR_OPTIONS = formatOptions([12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]);
22 | const AM_PM_OPTIONS = [{label: 'A.M.', value: 'AM'}, {label: 'P.M.', value: 'PM'}];
23 | const MINUTE_OPTIONS = formatOptions([{label: '00', value: 0}, 15, 30, 45]);
24 | const DATE_OPTIONS = formatOptions(range(1, 29));
25 | const DAY_OPTIONS = [
26 | {label: 'Sunday', value: 'SUN'},
27 | {label: 'Monday', value: 'MON'},
28 | {label: 'Tuesday', value: 'TUE'},
29 | {label: 'Wednesday', value: 'WED'},
30 | {label: 'Thursday', value: 'THU'},
31 | {label: 'Friday', value: 'FRI'},
32 | {label: 'Saturday', value: 'SAT'}
33 | ];
34 |
35 | export const HourInput = props => ;
36 | HourInput.propTypes = {
37 | value: PropTypes.number.isRequired,
38 | onChange: PropTypes.func.isRequired
39 | };
40 |
41 | export const MinuteInput = props => (
42 |
43 | );
44 | MinuteInput.propTypes = {
45 | value: PropTypes.number.isRequired,
46 | onChange: PropTypes.func.isRequired
47 | };
48 |
49 | export const DayInput = props => ;
50 | DayInput.propTypes = {
51 | value: PropTypes.number.isRequired,
52 | onChange: PropTypes.func.isRequired
53 | };
54 |
55 | export const AmPmInput = props => (
56 |
57 | );
58 | AmPmInput.propTypes = {
59 | value: PropTypes.string.isRequired,
60 | onChange: PropTypes.func.isRequired
61 | };
62 |
63 | export const DateInput = props => ;
64 | DateInput.propTypes = {
65 | value: PropTypes.number.isRequired,
66 | onChange: PropTypes.func.isRequired
67 | };
68 |
--------------------------------------------------------------------------------
/app/components/Settings/scheduler/modals/create-modal/create-modal.css:
--------------------------------------------------------------------------------
1 | .create-modal .container {
2 | background: #F5F7FB;
3 | }
4 |
5 | .create-modal .innerColumn {
6 | background: #fff;
7 | position: relative;
8 | }
9 |
10 | .create-modal .header {
11 | margin-bottom: 16px;
12 | padding: 0 32px;
13 | }
14 |
15 | .create-modal .button {
16 | position: absolute;
17 | top: 16px;
18 | right: 16px;
19 | padding: 2px 4px;
20 | }
21 |
22 | .create-modal .close-btn {
23 | line-height: 14px;
24 | min-height: 16px;
25 | }
26 |
27 | .create-modal .detailsColumn {
28 | padding: 0 32px;
29 | }
30 |
31 | .create-modal p {
32 | margin: 16px 0;
33 | padding: 16px;
34 | }
35 |
36 | .create-modal .submit {
37 | width: auto;
38 | margin: 24px 32px 16px;
39 | min-height: 28px;
40 | }
41 |
42 | .create-modal .save-warning {
43 | font-size: 12px;
44 | margin: 0 32px 16px;
45 | opacity: 0.7;
46 | }
47 |
48 | .create-modal .row {
49 | justify-content: flex-start;
50 | }
51 |
52 | .create-modal .Select-input > input {
53 | margin: 0 auto;
54 | width: 100%;
55 | height: auto;
56 | }
57 |
58 | .create-modal .Select-create-option-placeholder, .create-modal .Select-create-option-placeholder > span {
59 | color: #506784 !important;
60 | }
61 |
62 | .create-modal .dropdown {
63 | padding: 0;
64 | margin-bottom: 16px;
65 | width: 100%;
66 | max-width: 432px;
67 | }
68 |
69 | .create-modal .row-header {
70 | width: 20%;
71 | }
72 |
73 | .create-modal .row-body {
74 | width: 80%;
75 | }
76 |
77 | .create-modal .row-body > pre {
78 | margin-top: 5px;
79 | overflow-y: auto;
80 | max-height: 300px;
81 | }
82 |
--------------------------------------------------------------------------------
/app/components/Settings/scheduler/modals/login-modal/login-modal.css:
--------------------------------------------------------------------------------
1 | .login-modal .container {
2 | background: #F5F7FB;
3 | position: relative;
4 | }
5 |
6 | .login-modal .header {
7 | margin-bottom: 16px;
8 | padding: 0 32px;
9 | }
10 |
11 | .login-modal .button {
12 | position: absolute;
13 | top: 16px;
14 | right: 16px;
15 | padding: 2px 4px;
16 | }
17 |
18 | .login-modal .close-btn {
19 | line-height: 14px;
20 | min-height: 16px;
21 | }
22 |
23 | .login-modal p {
24 | margin: 56px 0 8px;
25 | padding: 16px;
26 | }
27 |
28 | .login-modal .actions button {
29 | width: 50%;
30 | margin: 24px 32px 32px;
31 | }
32 |
--------------------------------------------------------------------------------
/app/components/Settings/scheduler/modals/login-modal/login-modal.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import {CopyToClipboard} from 'react-copy-to-clipboard';
4 |
5 | import {Row, Column} from '../../../../layout';
6 | import Modal from '../../../../modal';
7 |
8 | import './login-modal.css';
9 |
10 | const containerOverrideStyle = {width: '400px'};
11 | const successTextStyle = {color: '#00cc96'};
12 |
13 | class PromptLoginModal extends React.Component {
14 | static propTypes = {
15 | open: PropTypes.bool.isRequired,
16 | preview: PropTypes.object,
17 | onClickAway: PropTypes.func.isRequired,
18 | onSubmit: PropTypes.func.isRequired
19 | };
20 |
21 | constructor() {
22 | super();
23 | this.state = {
24 | copyCoolingDown: false
25 | };
26 |
27 | this.onCopy = this.onCopy.bind(this);
28 | }
29 |
30 | onCopy() {
31 | if (!this.state.copyCoolingDown) {
32 | this.setState({copyCoolingDown: true});
33 | setTimeout(() => this.setState({copyCoolingDown: false}), 2000);
34 | }
35 | }
36 |
37 | render() {
38 | return (
39 |
40 |
41 |
42 | ×
43 |
44 |
45 |
46 | To create a scheduled query, you'll need to be logged into Chart Studio.
47 |
48 |
49 | Note: logging in will reset your query, click the button below to copy the query to your
50 | clipboard.
51 |
52 |
53 |
54 |
55 | Log In
56 |
57 |
58 |
59 | {this.state.copyCoolingDown ? (
60 | Copied!
61 | ) : (
62 | 'Copy Query'
63 | )}
64 |
65 |
66 |
67 |
68 |
69 | );
70 | }
71 | }
72 |
73 | export default PromptLoginModal;
74 |
--------------------------------------------------------------------------------
/app/components/Settings/scheduler/modals/tags-modal/tags-modal.css:
--------------------------------------------------------------------------------
1 | .tag-modal .count {
2 | padding: 4px 16px 4px 0px;
3 | margin-right: 16px;
4 | border-right: 1px solid rgba(0, 0, 0, 0.12);
5 | }
6 |
7 | .tag-modal .note {
8 | font-size: 14px;
9 | color: #797979;
10 | }
11 |
12 | .createTag {
13 | margin: 24px 0 16px;
14 | padding: 0 24px;
15 | }
16 |
17 | .create-modal .createTag .tagName {
18 | margin: 0 24px;
19 | }
20 |
21 | .tagsContainer {
22 | border: 1px solid #c8d4e3;
23 |
24 | max-height: 328px;
25 | overflow-y: auto;
26 | }
27 |
28 | .scrollContainer {
29 | justify-content: flex-start !important;
30 | }
31 |
32 | .description.row-body {
33 | color: #797979;
34 | }
35 |
36 | .tagRow {
37 | flex-shrink: 0;
38 | background: white;
39 | padding: 16px 24px;
40 | border-bottom: 1px solid #c8d4e3;
41 | }
42 |
43 | .tagRow:last-of-type {
44 | border-bottom: none;
45 | }
46 |
47 | .color-box {
48 | cursor: pointer;
49 | margin: 0;
50 | width: 30px;
51 | height: 30px;
52 | margin-right: 24px;
53 | border: 1px solid #E0E7EF;
54 | box-sizing: border-box;
55 | border-radius: 3px;
56 | }
57 |
58 | .delete {
59 | cursor: pointer;
60 | color: #FF5E5E;
61 | margin-left: auto;
62 | font-size: 24px;
63 | line-height: 1;
64 | }
65 |
66 | .tagRow .delete-button {
67 | background: #FF5E5E;
68 | border: none;
69 | margin-right: 0;
70 | }
71 |
72 | .chrome-picker input {
73 | /* reset styles */
74 | margin: initial !important;
75 | padding: initial;
76 | width: initial;
77 | float: none;
78 | }
79 |
--------------------------------------------------------------------------------
/app/components/Settings/scheduler/pickers/color-picker.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import enhanceWithClickOutside from 'react-click-outside';
4 | import {ChromePicker} from 'react-color';
5 |
6 | class ColorPicker extends React.Component {
7 | static propTypes = {
8 | color: PropTypes.string,
9 | onClickAway: PropTypes.func
10 | };
11 |
12 | static defaultProps = {
13 | onClickAway: () => {}
14 | };
15 |
16 | handleClickOutside(e) {
17 | if (this.state.open) {
18 | this.setState({open: false});
19 | this.props.onClickAway(e);
20 | }
21 | }
22 |
23 | constructor(props) {
24 | super(props);
25 | this.state = {
26 | open: false
27 | };
28 | this.openColorPicker = this.openColorPicker.bind(this);
29 | }
30 |
31 | openColorPicker() {
32 | this.setState({open: true});
33 | }
34 |
35 | render() {
36 | return (
37 |
38 | {this.state.open && (
39 |
40 |
41 |
42 | )}
43 |
44 | );
45 | }
46 | }
47 |
48 | export default enhanceWithClickOutside(ColorPicker);
49 |
--------------------------------------------------------------------------------
/app/components/Settings/scheduler/presentational/api-call-counts.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const formatCalls = count => count.toLocaleString('en');
5 | const pluralize = (word, num) => (num > 1 ? `${word}s` : word);
6 |
7 | export const AdditionalCallsPreview = props => {
8 | const {additionalCalls, currTotal} = props;
9 | const newTotal = additionalCalls + currTotal;
10 |
11 | return (
12 |
13 | API Usage: {formatCalls(additionalCalls)} {pluralize('call', additionalCalls)}
14 | /day (new total: {formatCalls(newTotal)})
15 |
16 | );
17 | };
18 | AdditionalCallsPreview.propTypes = {
19 | additionalCalls: PropTypes.number.isRequired,
20 | currTotal: PropTypes.number.isRequired
21 | };
22 | AdditionalCallsPreview.defaultProps = {
23 | additionalCalls: 0,
24 | currTotal: 0
25 | };
26 |
27 | export const toIndividualCallCountString = count => `${formatCalls(count)} API ${pluralize('call', count)} per day`;
28 |
29 | export const IndividualCallCount = ({count}) => {
30 | return {toIndividualCallCountString(count)} ;
31 | };
32 | IndividualCallCount.propTypes = {
33 | count: PropTypes.number.isRequired
34 | };
35 |
36 | export const CallCountWidget = ({count}) => {
37 | return (
38 |
39 |
40 |
46 | API USAGE
47 |
48 |
49 | {formatCalls(count)} {pluralize('call', count)}
50 | /day
51 |
52 | (for this connection)
53 |
54 |
55 | );
56 | };
57 | CallCountWidget.propTypes = {
58 | count: PropTypes.number.isRequired
59 | };
60 |
--------------------------------------------------------------------------------
/app/components/Settings/scheduler/presentational/execution-details.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import ReactToolTip from 'react-tooltip';
4 | import ms from 'ms';
5 | import moment from 'moment';
6 | import pluralize from 'pluralize';
7 |
8 | const ExecutionDetails = props => {
9 | return (
10 |
11 | {pluralize('row', props.rowCount || 0, true)}
12 | {' in '}
13 | 5 * 60 // 5 minutes
16 | ? `completed execution at ${moment(props.completedAt).format('h:mm a')}`
17 | : ''
18 | }
19 | >
20 | {ms(props.duration * 1000, {long: true})}
21 |
22 |
23 |
24 | );
25 | };
26 |
27 | ExecutionDetails.propTypes = {
28 | duration: PropTypes.number,
29 | rowCount: PropTypes.number,
30 | completedAt: PropTypes.number
31 | };
32 |
33 | export default ExecutionDetails;
34 |
--------------------------------------------------------------------------------
/app/components/Settings/scheduler/presentational/request-error.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import ErrorMessage from '../../../error';
4 |
5 | const propTypes = {
6 | children: PropTypes.string,
7 | onClick: PropTypes.func
8 | };
9 |
10 | const outlineStyle = {outline: 'none'};
11 |
12 | function capitalize(s = '') {
13 | return s[0].toUpperCase() + s.slice(1);
14 | }
15 |
16 | function FormattedMessage(props) {
17 | const content = capitalize(props.children);
18 |
19 | if (content.toLowerCase().includes('syntax')) {
20 | return (
21 |
22 | {capitalize(content.replace('QueryExecutionError: ', ''))}.
23 |
24 | Syntax errors are usually easy to fix. We recommend making sure the query is correct in the{' '}
25 | preview editor before scheduling it.
26 |
27 | );
28 | }
29 |
30 | if (content.startsWith('QueryExecutionError')) {
31 | return (
32 |
33 |
34 | An error occurred while executing the query. This is may be an issue with the query itself.
35 |
36 | {content}
37 |
38 | );
39 | }
40 |
41 | if (content.startsWith('PlotlyApiError')) {
42 | return (
43 |
44 |
45 | An unexpected error occurred syncing the query to the Chart Studio. Please try again later.
46 |
47 | {content}
48 |
49 | );
50 | }
51 |
52 | if (content.startsWith('MetadataError')) {
53 | return (
54 |
55 |
56 | An unexpected error occurred while uploading query metadata. Please try again now.
57 |
58 | {capitalize(content.replace('MetadataError: ', ''))}
59 |
60 | );
61 | }
62 |
63 | return content.slice(0, 100);
64 | }
65 |
66 | FormattedMessage.propTypes = propTypes;
67 |
68 | function RequestError(props) {
69 | return (
70 |
71 |
72 |
73 | );
74 | }
75 |
76 | RequestError.propTypes = propTypes;
77 |
78 | export default RequestError;
79 |
--------------------------------------------------------------------------------
/app/components/Settings/scheduler/presentational/sql.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import Highlight from 'react-highlight';
4 |
5 | export const SQL = props => {props.children} ;
6 |
7 | SQL.propTypes = {
8 | children: PropTypes.string,
9 | className: PropTypes.string
10 | };
11 |
12 | export default SQL;
13 |
--------------------------------------------------------------------------------
/app/components/Settings/scheduler/presentational/status.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable max-len */
2 | import React from 'react';
3 | import PropTypes from 'prop-types';
4 |
5 | import {EXE_STATUS} from '../../../../../shared/constants.js';
6 |
7 | const Status = ({status, size}) => {
8 | if (status === EXE_STATUS.ok) {
9 | return ;
10 | }
11 |
12 | if (status === EXE_STATUS.running) {
13 | return ;
14 | }
15 |
16 | return ;
17 | };
18 |
19 | Status.propTypes = {
20 | status: PropTypes.string,
21 | size: PropTypes.number
22 | };
23 |
24 | Status.defaultProps = {
25 | status: EXE_STATUS.ok
26 | };
27 |
28 | export default Status;
29 |
--------------------------------------------------------------------------------
/app/components/Settings/scheduler/presentational/tag.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | /* eslint-disable */
5 | const Tag = ({name, color, style, className}) => (
6 |
21 | {name}
22 |
23 | );
24 |
25 | Tag.propTypes = {
26 | name: PropTypes.string.isRequired,
27 | color: PropTypes.string
28 | };
29 |
30 | export default Tag;
31 |
--------------------------------------------------------------------------------
/app/components/Settings/scheduler/presentational/timed-message.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | class TimedMessage extends React.Component {
5 | static propTypes = {
6 | children: PropTypes.node,
7 | timeout: PropTypes.number.isRequired
8 | };
9 | static defaultProps = {
10 | timeout: 5000
11 | };
12 |
13 | constructor(props) {
14 | super(props);
15 |
16 | this.state = {
17 | show: false
18 | };
19 |
20 | const component = this;
21 | this.timer = setTimeout(function() {
22 | component.setState({show: true});
23 | }, this.props.timeout);
24 | }
25 |
26 | componentWillUnmount() {
27 | clearTimeout(this.timer);
28 | }
29 |
30 | render() {
31 | if (this.state.show) {
32 | return this.props.children;
33 | }
34 |
35 | return null;
36 | }
37 | }
38 |
39 | export default TimedMessage;
40 |
--------------------------------------------------------------------------------
/app/components/Settings/scheduler/presentational/timestamp.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import ReactToolTip from 'react-tooltip';
4 | import moment from 'moment';
5 |
6 | const toIso = timestamp => {
7 | const date = new Date(timestamp);
8 |
9 | return date.getTime() > 0 ? date.toISOString() : 'Invalid ISO';
10 | };
11 |
12 | const formatAbsolute = (timestamp, inline) => {
13 | const start = new Date();
14 | const end = new Date();
15 | start.setHours(0, 0, 0, 0);
16 | end.setHours(23, 59, 59, 999);
17 |
18 | const startOfToday = start.getTime();
19 | const endOfToday = end.getTime();
20 |
21 | const isToday = timestamp >= startOfToday && timestamp <= endOfToday;
22 |
23 | return isToday
24 | ? `${inline ? 't' : 'T'}oday at ${moment(timestamp).format('h:mm a')}`
25 | : `${moment(timestamp).format('h:mm a')} on ${moment(timestamp).format('MMM Do')}`;
26 | };
27 |
28 | const formatRelative = ({value, checkIfRunning}) => {
29 | if (checkIfRunning && value < Date.now()) {
30 | return 'Currently running';
31 | }
32 |
33 | return moment(value).fromNow();
34 | };
35 |
36 | const Timestamp = props => {
37 | const {value, checkIfRunning, inline} = props;
38 | return (
39 |
40 | {formatRelative({value, checkIfRunning})}
41 | ({toIso(props.value)})
42 |
43 |
44 | );
45 | };
46 |
47 | Timestamp.propTypes = {
48 | value: PropTypes.number,
49 | checkIfRunning: PropTypes.bool,
50 | inline: PropTypes.bool
51 | };
52 |
53 | export default Timestamp;
54 |
--------------------------------------------------------------------------------
/app/components/Settings/scheduler/scheduler.css:
--------------------------------------------------------------------------------
1 | /* SQL token highlighting */
2 | .default .hljs-keyword,
3 | .default .hljs-selector-tag {
4 | color: #ab63fa;
5 | }
6 |
7 | .default .hljs-number,
8 | .default .hljs-meta,
9 | .default .hljs-built_in,
10 | .default .hljs-builtin-name,
11 | .default .hljs-literal,
12 | .default .hljs-type,
13 | .default .hljs-params {
14 | color: #00cc96;
15 | }
16 |
17 | .default .hljs-string,
18 | .default .hljs-symbol,
19 | .default .hljs-bullet {
20 | color: #119DFF;
21 | }
22 |
23 | .default .hljs {
24 | color: #585260;
25 | }
26 |
27 | .bold .hljs-keyword,
28 | .bold .hljs-selector-tag {
29 | font-weight: bold;
30 | }
31 |
32 | .scheduler pre {
33 | white-space: pre-wrap;
34 | }
35 |
36 | /* custom ReactDataGrid styles for scheduled queries table */
37 | .scheduler-table .react-grid-Row--odd .react-grid-Cell {
38 | background: #fff;
39 | }
40 |
41 | .scheduler-table .react-grid-Row--odd:hover .react-grid-Cell {
42 | background-color: #f9f9f9;
43 | }
44 |
45 | /* fix horizontal overflow issues */
46 | .scheduler-table .react-grid-Cell {
47 | box-sizing: border-box;
48 | border-right: none;
49 | }
50 |
51 | .scheduler-table .react-grid-Row {
52 | cursor: pointer;
53 | }
54 |
55 | .scheduler-table .react-grid-Grid {
56 | min-height: calc(100vh - 380px) !important;
57 | }
58 |
59 | .scheduler-table .react-grid-Canvas {
60 | height: calc(100vh - 352px) !important;
61 | }
62 |
63 | .scheduler-table pre {
64 | margin: 0;
65 | }
66 |
67 | .sql pre {
68 | margin: 0 auto;
69 | max-height: 100%;
70 | }
71 |
72 | .scheduler .CodeMirror {
73 | width: 100%;
74 | }
75 |
76 | .ellipsis {
77 | text-overflow: ellipsis;
78 | white-space: nowrap;
79 | overflow: hidden;
80 | }
81 |
82 | .ellipsis pre {
83 | text-overflow: ellipsis;
84 | white-space: nowrap;
85 | overflow: hidden;
86 | }
87 |
88 | .meta-preview button {
89 | min-height: 28px;
90 | }
91 |
92 | .meta-preview pre {
93 | margin: 0;
94 | }
95 |
96 | .meta-preview .Select-input > input {
97 | margin: 0 auto;
98 | width: 100%;
99 | height: auto;
100 | }
101 |
102 | .meta-preview .Select-create-option-placeholder, .meta-preview .Select-create-option-placeholder > span {
103 | color: #506784 !important;
104 | }
105 |
106 | .tag-manager-text {
107 | cursor: pointer;
108 | font-size: 12px;
109 | color: #9D9D9D;
110 | }
111 |
--------------------------------------------------------------------------------
/app/components/error.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const style = {height: 'auto'};
5 | const ErrorMessage = props => {props.children}
;
6 | ErrorMessage.propTypes = {
7 | children: PropTypes.node.isRequired
8 | };
9 |
10 | export default ErrorMessage;
11 |
--------------------------------------------------------------------------------
/app/components/layout.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * This module contains a collection of helpful primitive components
3 | * for layout purposes.
4 | */
5 | import React from 'react';
6 | import PropTypes from 'prop-types';
7 |
8 | export const Row = props => (
9 |
20 | {props.children}
21 |
22 | );
23 |
24 | Row.propTypes = {
25 | children: PropTypes.node,
26 | style: PropTypes.object
27 | };
28 |
29 | export const Column = props => (
30 |
35 | );
36 |
37 | Column.propTypes = {
38 | style: PropTypes.object
39 | };
40 |
--------------------------------------------------------------------------------
/app/components/modal.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import enhanceWithClickOutside from 'react-click-outside';
5 |
6 | const ClickAway = enhanceWithClickOutside(
7 | class extends Component {
8 | static propTypes = {
9 | onClickAway: PropTypes.func,
10 | children: PropTypes.node
11 | }
12 | handleClickOutside(e) {
13 | this.props.onClickAway(e);
14 | }
15 | render() {
16 | return this.props.children;
17 | }
18 | }
19 | );
20 |
21 | const Modal = props =>
22 | props.open ? (
23 |
39 |
40 | {props.children}
41 |
42 |
43 | ) : null;
44 |
45 | Modal.propTypes = {
46 | children: PropTypes.node,
47 | onClickAway: PropTypes.func,
48 | open: PropTypes.bool,
49 | className: PropTypes.string
50 | };
51 |
52 | export default Modal;
53 |
--------------------------------------------------------------------------------
/app/components/success.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const style = {fontSize: 16};
5 | const SuccessMessage = (props) => (
6 |
7 |
{props.message}
8 | {props.children}
9 |
10 | );
11 |
12 | SuccessMessage.propTypes = {
13 | children: PropTypes.node,
14 | message: PropTypes.string
15 | };
16 |
17 | export default SuccessMessage;
18 |
--------------------------------------------------------------------------------
/app/containers/DevTools.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createDevTools } from 'redux-devtools';
3 | import LogMonitor from 'redux-devtools-log-monitor';
4 | import DockMonitor from 'redux-devtools-dock-monitor';
5 |
6 | export default createDevTools(
7 |
11 |
12 |
13 | );
14 |
--------------------------------------------------------------------------------
/app/icons/app.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/icons/app.icns
--------------------------------------------------------------------------------
/app/icons/app.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/icons/app.ico
--------------------------------------------------------------------------------
/app/icons/app_1024x1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/icons/app_1024x1024.png
--------------------------------------------------------------------------------
/app/icons/app_16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/icons/app_16x16.png
--------------------------------------------------------------------------------
/app/icons/app_256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/icons/app_256x256.png
--------------------------------------------------------------------------------
/app/icons/app_32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/icons/app_32x32.png
--------------------------------------------------------------------------------
/app/icons/app_512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/icons/app_512x512.png
--------------------------------------------------------------------------------
/app/images/add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/images/add.png
--------------------------------------------------------------------------------
/app/images/apache_drill-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/images/apache_drill-logo.png
--------------------------------------------------------------------------------
/app/images/athena-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/images/athena-logo.png
--------------------------------------------------------------------------------
/app/images/bigquery-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/images/bigquery-logo.png
--------------------------------------------------------------------------------
/app/images/checkmark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/images/checkmark.png
--------------------------------------------------------------------------------
/app/images/clickhouse-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/images/clickhouse-logo.png
--------------------------------------------------------------------------------
/app/images/csv-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/images/csv-logo.png
--------------------------------------------------------------------------------
/app/images/dataworld-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/images/dataworld-logo.png
--------------------------------------------------------------------------------
/app/images/delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/images/delete.png
--------------------------------------------------------------------------------
/app/images/elastic-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/images/elastic-logo.png
--------------------------------------------------------------------------------
/app/images/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/images/favicon.ico
--------------------------------------------------------------------------------
/app/images/green-dot.svg:
--------------------------------------------------------------------------------
1 | icon-status_online_01
--------------------------------------------------------------------------------
/app/images/ibmdb2-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/images/ibmdb2-logo.png
--------------------------------------------------------------------------------
/app/images/impala-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/images/impala-logo.png
--------------------------------------------------------------------------------
/app/images/import-modal-http.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/images/import-modal-http.png
--------------------------------------------------------------------------------
/app/images/import-modal-https.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/images/import-modal-https.png
--------------------------------------------------------------------------------
/app/images/livy-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/images/livy-logo.png
--------------------------------------------------------------------------------
/app/images/mariadb-logo-small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/images/mariadb-logo-small.png
--------------------------------------------------------------------------------
/app/images/mariadb-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/images/mariadb-logo.png
--------------------------------------------------------------------------------
/app/images/mssql-logo-small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/images/mssql-logo-small.png
--------------------------------------------------------------------------------
/app/images/mssql-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/images/mssql-logo.png
--------------------------------------------------------------------------------
/app/images/mysql-logo-small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/images/mysql-logo-small.png
--------------------------------------------------------------------------------
/app/images/mysql-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/images/mysql-logo.png
--------------------------------------------------------------------------------
/app/images/oracle-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/images/oracle-logo.png
--------------------------------------------------------------------------------
/app/images/plotly-connector-logo.svg:
--------------------------------------------------------------------------------
1 | dbconnector
--------------------------------------------------------------------------------
/app/images/plotly-logo-no-name.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/images/plotly-logo-no-name.png
--------------------------------------------------------------------------------
/app/images/plotly-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/images/plotly-logo.png
--------------------------------------------------------------------------------
/app/images/postgres-logo-small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/images/postgres-logo-small.png
--------------------------------------------------------------------------------
/app/images/postgres-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/images/postgres-logo.png
--------------------------------------------------------------------------------
/app/images/red-dot.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | reddot
5 |
6 | Calque 1
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/images/redshift-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/images/redshift-logo.png
--------------------------------------------------------------------------------
/app/images/running.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/images/running.png
--------------------------------------------------------------------------------
/app/images/s3-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/images/s3-logo.png
--------------------------------------------------------------------------------
/app/images/spark-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/images/spark-logo.png
--------------------------------------------------------------------------------
/app/images/sqlite-logo-small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/images/sqlite-logo-small.png
--------------------------------------------------------------------------------
/app/images/sqlite-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/images/sqlite-logo.png
--------------------------------------------------------------------------------
/app/images/unfold.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/images/unfold.png
--------------------------------------------------------------------------------
/app/images/x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/app/images/x.png
--------------------------------------------------------------------------------
/app/images/yellow-dot.svg:
--------------------------------------------------------------------------------
1 |
2 | yellow dot
3 |
4 | Layer 1
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | // Workaround to use `react-router@3` in React 16
4 | import PropTypes from 'prop-types'; // eslint-disable-line no-unused-vars
5 |
6 | import {render} from 'react-dom';
7 | import {Provider} from 'react-redux';
8 | import {Route, Router, browserHistory} from 'react-router';
9 |
10 | import configureStore from './store/configureStore';
11 | import {build, version} from '../package.json';
12 |
13 | import Login from './components/Login.react';
14 | import Configuration from './components/Configuration.react';
15 | import Status from './components/Oauth.react';
16 | import {homeUrl, isOnPrem} from './utils/utils';
17 |
18 | const store = configureStore();
19 |
20 | window.document.title = isOnPrem() ?
21 | `${build.productName}` :
22 | `${build.productName} v${version}`;
23 |
24 | render(
25 |
26 |
27 |
28 |
29 |
30 |
31 | ,
32 | document.getElementById('root')
33 | );
34 |
--------------------------------------------------------------------------------
/app/store/configureStore.development.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware, compose } from 'redux';
2 | import { persistState } from 'redux-devtools';
3 | import thunk from 'redux-thunk';
4 | import createLogger from 'redux-logger';
5 | import rootReducer from '../reducers';
6 | import DevTools from '../containers/DevTools';
7 |
8 | const logger = createLogger({
9 | level: 'info',
10 | collapsed: true
11 | });
12 |
13 | const enhancer = compose(
14 | applyMiddleware(thunk, logger),
15 | // electronEnhancer(true), // ({test: true}),
16 | DevTools.instrument(),
17 | persistState(
18 | window.location.href.match(
19 | /[?&]debug_session=([^&]+)\b/
20 | )
21 | ),
22 | window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
23 | );
24 |
25 | export default function configureStore(initialState) {
26 | const store = createStore(rootReducer, initialState, enhancer);
27 |
28 | if (module.hot) {
29 | module.hot.accept('../reducers', () =>
30 | store.replaceReducer(require('../reducers'))
31 | );
32 | }
33 |
34 | window.store = store;
35 | return store;
36 | }
37 |
--------------------------------------------------------------------------------
/app/store/configureStore.js:
--------------------------------------------------------------------------------
1 | if (process.env.NODE_ENV === 'production') {
2 | module.exports = require('./configureStore.production');
3 | } else {
4 | module.exports = require('./configureStore.development');
5 | }
6 |
--------------------------------------------------------------------------------
/app/store/configureStore.production.js:
--------------------------------------------------------------------------------
1 | import {createStore, applyMiddleware} from 'redux';
2 | import thunk from 'redux-thunk';
3 | import rootReducer from '../reducers';
4 |
5 | const enhancer = applyMiddleware(thunk);
6 |
7 | export default function configureStore(initialState) {
8 | const store = createStore(rootReducer, initialState, enhancer);
9 |
10 | window.store = store;
11 |
12 | return store;
13 | }
14 |
--------------------------------------------------------------------------------
/app/utils/queryUtils.js:
--------------------------------------------------------------------------------
1 | export function mapQueryToDailyCallCount (query) {
2 | const { cronInterval, refreshInterval } = query || {};
3 | if (!cronInterval && !refreshInterval) {
4 | return 0;
5 | }
6 |
7 | if (cronInterval) {
8 | if (cronInterval === '* * * * *') {
9 | return 1440; // number of minutes in a day
10 | } else if (cronInterval === '*/5 * * * *') {
11 | return 288; // number of 5 minute blocks per day
12 | } else if (cronInterval.match(/\S+? \* \* \* \*/)) {
13 | return 24; // number of hours in a day
14 | }
15 |
16 | // could be daily or weekly. Deliberately return same amount either way.
17 | // Provides worst-case estimate for user.
18 | return 1;
19 | }
20 |
21 | // otherwise, fallback to refresh interval
22 | return Math.floor(86400 / refreshInterval); // seconds per day / scheduling interval in seconds
23 | }
24 |
--------------------------------------------------------------------------------
/app/utils/utils.js:
--------------------------------------------------------------------------------
1 | import {contains} from 'ramda';
2 |
3 | export function isElectron() {
4 | // the electron's main process preloads a script that sets up `window.$falcon`
5 | return Boolean(window.$falcon);
6 | }
7 |
8 | export function setUsernameListener(callback) {
9 | if (isElectron()) {
10 | window.$falcon.setUsernameListener(callback);
11 | }
12 | }
13 |
14 | export function showOpenDialog(options, callback) {
15 | if (isElectron()) {
16 | return window.$falcon.showOpenDialog(options, callback);
17 | }
18 | }
19 |
20 | export function baseUrl() {
21 | /*
22 | * Return the base URL of the app.
23 | * If the user is running this app locally and is configuring their database,
24 | * then they will be at https://their-subdomain.plot.ly/database-connector
25 | * and this function will return https://their-subdomain.plot.ly
26 | * If the user is on on-prem, then the connector is prefixed behind
27 | * /external-data-connector, so they will be configuring their connections at
28 | * https://plotly.acme.com/external-data-connector/database-connector and this function
29 | * will return https://plotly.acme.com/external-data-connector/
30 | */
31 | let url = window.location.href;
32 | if (url.endsWith('/')) {
33 | url = url.slice(0, url.length - 1);
34 | }
35 | if (url.endsWith('database-connector')) {
36 | url = url.slice(0, url.length - 'database-connector'.length);
37 | }
38 | return url;
39 | }
40 |
41 | export function usesHttpsProtocol() {
42 | return contains('https://', baseUrl());
43 | }
44 |
45 | export function isOnPrem() {
46 | return (contains('external-data-connector', baseUrl()));
47 | }
48 |
49 | export function plotlyUrl() {
50 | if (isOnPrem()) {
51 | return window.location.origin;
52 | }
53 | return 'https://plot.ly';
54 | }
55 |
56 | export function homeUrl() {
57 | return (isOnPrem()) ?
58 | '/external-data-connector' :
59 | '';
60 | }
61 |
62 | export function datasetUrl(fid) {
63 | const [account, gridId] = fid.split(':');
64 | return `${plotlyUrl()}/~${account}/${gridId}`;
65 | }
66 |
67 | export function getPathNames(url) {
68 | const parser = document.createElement('a');
69 | parser.href = url;
70 | const pathNames = parser.pathname.split('/');
71 |
72 | return pathNames;
73 | }
74 |
75 | export function decapitalize(startString) {
76 | if (startString.length === 0) {
77 | return '';
78 | }
79 | return startString.slice(0, 1).toLowerCase() + startString.slice(1);
80 | }
81 |
--------------------------------------------------------------------------------
/app/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | version: 0.4.{build}
2 |
3 | environment:
4 | matrix:
5 | - nodejs_version: 8
6 | POSTGRES_PATH: C:\Program Files\PostgreSQL\9.6
7 |
8 | platform:
9 | - x64
10 |
11 | services:
12 | - postgresql96
13 |
14 | cache:
15 | - '%USERPROFILE%\.electron'
16 |
17 | init:
18 | - git config --global core.autocrlf input
19 | - SET PATH=%POSTGRES_PATH%\bin;%PATH%
20 |
21 |
22 | install:
23 | - ps: Install-Product node $env:nodejs_version $env:platform
24 | - yarn install --force
25 |
26 | build_script:
27 | - yarn run build
28 | - yarn run pack
29 |
30 | after_build:
31 | - ps: |
32 | $FALCON_VERSION = node -p "require('./package.json').version"
33 | cd release
34 | 7z a win-falcon-v$FALCON_VERSION.zip *.exe
35 |
36 | artifacts:
37 | - path: release\*.zip
38 |
39 | test: off
40 |
--------------------------------------------------------------------------------
/backend/constants.js:
--------------------------------------------------------------------------------
1 | import {getSetting} from './settings';
2 |
3 | export function getUnsecuredCookieOptions() {
4 | return {
5 | path: getSetting('WEB_BASE_PATHNAME')
6 | };
7 | }
8 |
9 | export function getCookieOptions() {
10 | return {
11 | secure: getSetting('SSL_ENABLED'),
12 | path: getSetting('WEB_BASE_PATHNAME')
13 | };
14 | }
15 |
16 | export function getAccessTokenCookieOptions() {
17 | return {
18 | secure: getSetting('SSL_ENABLED'),
19 | path: getSetting('WEB_BASE_PATHNAME'),
20 | maxAge: getSetting('ACCESS_TOKEN_AGE')
21 | };
22 | }
23 |
--------------------------------------------------------------------------------
/backend/headless.development.js:
--------------------------------------------------------------------------------
1 | // Entry point for running the app without electron
2 | import Logger from './logger';
3 | import Servers from './routes.js';
4 | import {getSetting} from './settings.js';
5 |
6 | /*
7 | * If running on the same machine as plotly on-premise,
8 | * then we do not need to verify SSL certificates as
9 | * requests to plotly will never leave the server.
10 | *
11 | * This is to allow users to use Plotly On-Prem with
12 | * a self-signed certificate generated by replicated
13 | * or by themselves without actually specifying the
14 | * path of the actual certificate.
15 | * See https://github.com/plotly/streambed/issues/8948.
16 | *
17 | * Note: if any requests are made to other domains
18 | * or if any other services end up running inside
19 | * this docker container, then this setting should
20 | * be applied on a request-by-request basis as an
21 | * argument of the `fetch` `agent` instead of
22 | * set as a process variable.
23 | */
24 | if (getSetting('IS_RUNNING_INSIDE_ON_PREM')) {
25 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
26 | }
27 |
28 | const servers = new Servers();
29 | Logger.log('Starting server', 2);
30 | servers.httpServer.start();
31 | Logger.log('Loading persistent queries', 2);
32 | servers.queryScheduler.loadQueries();
33 |
--------------------------------------------------------------------------------
/backend/init.js:
--------------------------------------------------------------------------------
1 | import Logger from './logger';
2 |
3 | import {
4 | deleteAllConnections,
5 | deleteBadConnections
6 | } from './persistent/Connections.js';
7 | import {getSetting} from './settings.js';
8 |
9 | const setCSVStorageSize = require('./persistent/datastores/csv.js').setStorageSize;
10 |
11 | export default function init() {
12 | try {
13 | deleteBadConnections();
14 | } catch (error) {
15 | Logger.log(`Failed to delete bad connections: ${error.message}`);
16 | deleteAllConnections();
17 | }
18 |
19 | try {
20 | setCSVStorageSize(getSetting('CSV_STORAGE_SIZE'));
21 | } catch (error) {
22 | Logger.log(`Failed to get setting CSV_STORAGE_SIZE: ${error.message}`);
23 | setCSVStorageSize(0);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/backend/logger.js:
--------------------------------------------------------------------------------
1 | import bunyan from 'bunyan';
2 | import {createStoragePath} from './utils/homeFiles';
3 | import {getSetting} from './settings';
4 |
5 | // TODO - Set max size of the log file
6 | // Note: class is named "Loggr" because name "Logger" would cause
7 | // `import/no-named-as-default` ESLint rule to complain when
8 | // the default logger (i.e. this module's default export) would
9 | // be imported as `import Logger from 'logger'`.
10 | export class Loggr {
11 | constructor() {
12 | createStoragePath();
13 | let streams;
14 | if (getSetting('LOG_TO_STDOUT')) {
15 | streams = [{
16 | level: 'info',
17 | stream: process.stdout
18 | }];
19 | } else {
20 | streams = [{
21 | level: 'info',
22 | path: getSetting('LOG_PATH')
23 | }];
24 | }
25 |
26 | this.logToFile = bunyan.createLogger({
27 | name: 'plotly-database-connector-logger',
28 | streams
29 | });
30 |
31 | this.log = (logEntry, code = 2) => {
32 | switch (code) {
33 | case 0:
34 | this.logToFile.error(logEntry);
35 | break;
36 | case 1:
37 | this.logToFile.warn(logEntry);
38 | break;
39 | case 2:
40 | this.logToFile.info(logEntry);
41 | break;
42 | default:
43 | this.logToFile.info(logEntry);
44 | }
45 | };
46 |
47 | }
48 | }
49 |
50 | export default new Loggr();
51 |
--------------------------------------------------------------------------------
/backend/persistent/Queries.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import {findIndex, propEq} from 'ramda';
3 | import YAML from 'yamljs';
4 |
5 | import {getSetting} from '../settings.js';
6 | import {createStoragePath} from '../utils/homeFiles';
7 | import {stripUndefinedKeys} from '../utils/persistenceUtils.js';
8 |
9 | export function getQuery(fid) {
10 | const queries = getQueries();
11 | return queries.find(query => query.fid === fid);
12 | }
13 |
14 | export function getQueries() {
15 | if (fs.existsSync(getSetting('QUERIES_PATH'))) {
16 | return YAML.load(getSetting('QUERIES_PATH').toString());
17 | }
18 | return [];
19 | }
20 |
21 | export function saveQuery(queryObject) {
22 | const queries = getQueries();
23 | queries.push(stripUndefinedKeys(queryObject));
24 | if (!fs.existsSync(getSetting('STORAGE_PATH'))) {
25 | createStoragePath();
26 | }
27 | fs.writeFileSync(getSetting('QUERIES_PATH'), YAML.stringify(queries, 4));
28 | }
29 |
30 | export function updateQuery(fid, updatedQueryData) {
31 | const existingQuery = getQuery(fid);
32 | if (!existingQuery) {
33 | // don't allow appending data to query that doesn't exist
34 | return;
35 | }
36 |
37 | const updatedQuery = stripUndefinedKeys({
38 | ...existingQuery,
39 | ...updatedQueryData
40 | });
41 |
42 | deleteQuery(fid);
43 | saveQuery(updatedQuery);
44 | }
45 |
46 | export function deleteQuery(fid) {
47 | const queries = getQueries();
48 | const index = findIndex(propEq('fid', fid), queries);
49 |
50 | if (index > -1) {
51 | queries.splice(index, 1);
52 | fs.writeFileSync(getSetting('QUERIES_PATH'), YAML.stringify(queries, 4));
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/backend/persistent/Tags.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import shortid from 'shortid';
3 | import {findIndex, propEq} from 'ramda';
4 | import YAML from 'yamljs';
5 |
6 | import {getSetting} from '../settings.js';
7 | import {createStoragePath} from '../utils/homeFiles';
8 | import {stripUndefinedKeys} from '../utils/persistenceUtils.js';
9 |
10 | export const HEX_CODE_REGEX = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
11 | export const MAX_TAG_LENGTH = 30;
12 |
13 | export function getTag(id) {
14 | const tags = getTags();
15 | return tags.find(tag => tag.id === id);
16 | }
17 |
18 | export function getTags() {
19 | if (fs.existsSync(getSetting('TAGS_PATH'))) {
20 | return YAML.load(getSetting('TAGS_PATH').toString());
21 | }
22 | return [];
23 | }
24 |
25 | export function saveTag(newTag) {
26 | if (!newTag.id) {
27 | newTag.id = shortid.generate();
28 | }
29 |
30 | const tags = getTags();
31 | tags.push(newTag);
32 |
33 | if (!fs.existsSync(getSetting('STORAGE_PATH'))) {
34 | createStoragePath();
35 | }
36 | fs.writeFileSync(getSetting('TAGS_PATH'), YAML.stringify(tags, 4));
37 |
38 | return newTag;
39 | }
40 |
41 | export function updateTag(id, updatedTagData) {
42 | const existingTag = getTag(id);
43 | if (!existingTag) {
44 | // don't allow appending data to query that doesn't exist
45 | return;
46 | }
47 |
48 | const updatedTag = stripUndefinedKeys({
49 | ...existingTag,
50 | ...updatedTagData
51 | });
52 |
53 | deleteTag(id);
54 | return saveTag(updatedTag);
55 | }
56 |
57 | export function deleteTag(id) {
58 | const tags = getTags();
59 | const index = findIndex(propEq('id', id), tags);
60 |
61 | if (index > -1) {
62 | tags.splice(index, 1);
63 | fs.writeFileSync(getSetting('TAGS_PATH'), YAML.stringify(tags, 4));
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/backend/persistent/datastores/ApacheDrill.js:
--------------------------------------------------------------------------------
1 | import fetch from 'node-fetch';
2 | import * as S3 from './S3.js';
3 |
4 |
5 | export function connect(connection) {
6 | const {host, port} = connection;
7 | const url = `${host}:${port}/query.json`;
8 | return fetch(url);
9 | }
10 |
11 | export function query(queryStatement, connection) {
12 | const {host, port} = connection;
13 | const url = `${host}:${port}/query.json`;
14 |
15 | return new Promise((resolve, reject) => {
16 | fetch(url, {
17 | headers: {
18 | 'Accept': 'application/json',
19 | 'Content-Type': 'application/json'
20 | },
21 | method: 'POST',
22 | body: JSON.stringify({
23 | queryType: 'SQL',
24 | query: queryStatement
25 | })
26 | })
27 | .then(response => response.json())
28 | .then(json => {
29 | const {errorMessage, rows, columns} = json;
30 | if (errorMessage) {
31 | reject({message: errorMessage});
32 | } else {
33 | resolve({
34 | rows: rows.map(row => columns.map(c => row[c])),
35 | columnnames: columns
36 | });
37 | }
38 | })
39 | .catch(reject);
40 | });
41 |
42 | }
43 |
44 | export function storage(connection) {
45 | const {host, port} = connection;
46 | const url = `${host}:${port}/storage.json`;
47 | return fetch(url).then(res => res.json());
48 | }
49 |
50 | // TODO - Make this more flexible?
51 | export function listS3Files(connection) {
52 | return S3.files({
53 | bucket: connection.bucket,
54 | accessKeyId: connection.accessKeyId,
55 | secretAccessKey: connection.secretAccessKey
56 | });
57 | }
58 |
--------------------------------------------------------------------------------
/backend/persistent/datastores/Elasticsearch.js:
--------------------------------------------------------------------------------
1 | import {parseElasticsearch} from '../../parse';
2 | import fetch from 'node-fetch';
3 |
4 | const KEEP_ALIVE_FOR = '1m'; // minutes
5 | const MAX_RESULTS_SIZE = 10 * 1000;
6 |
7 | function request(relativeUrl, connection, {body, method, queryStringParams = ''}) {
8 | const {host, port, username, password} = connection;
9 | let url;
10 | if (typeof port !== 'undefined' && port !== '') {
11 | url = `${host}:${port}/${relativeUrl}?format=json${queryStringParams}`;
12 | } else {
13 | url = `${host}/${relativeUrl}?format=json${queryStringParams}`;
14 | }
15 | const headers = {
16 | 'Accept': 'application/json',
17 | 'Content-Type': 'application/json'
18 | };
19 | if (username && password) {
20 | headers.Authorization = 'Basic ' + new Buffer(
21 | username + ':' + password
22 | ).toString('base64');
23 | }
24 | return fetch(url, {
25 | headers,
26 | method,
27 | body: body ? JSON.stringify(body) : null
28 | });
29 | }
30 |
31 | export function connect(connection) {
32 | return request('_cat/indices/', connection, {method: 'GET'});
33 | }
34 |
35 | export function query(queryStmt, connection) {
36 | const queryObject = JSON.parse(queryStmt);
37 | const {body, index, type} = queryObject;
38 | /*
39 | * If size is not defined or smaller than 10K, keep scroll disabled and
40 | * let elasticsearch handle it, which it defaults right now to 10.
41 | * If it is, and it is higher than 10K then enable scroll.
42 | */
43 | const scrollEnabled = parseInt(body.size, 10) > MAX_RESULTS_SIZE;
44 | /*
45 | * In order to use scrolling, the initial search request should specify
46 | * the scroll parameter in the query string, which tells Elasticsearch
47 | * how long it should keep the “search context” alive.
48 | */
49 | const scrollParam = scrollEnabled ? `&scroll=${KEEP_ALIVE_FOR}` : '';
50 |
51 | return elasticsearchMappings(connection)
52 | .then(mappings => {
53 | const mapping = mappings[index].mappings[type].properties;
54 | return request(`${index}/${type}/_search`, connection, {
55 | body,
56 | method: 'POST',
57 | queryStringParams: scrollParam
58 | })
59 | .then(res => res.json().then(results => {
60 | if (res.status === 200) {
61 | return parseElasticsearch(body, results, mapping);
62 | }
63 | throw new Error(JSON.stringify(results));
64 | }));
65 | });
66 | }
67 |
68 | export function elasticsearchMappings(connection) {
69 | return request('_all/_mappings', connection, {method: 'GET'})
70 | .then(res => res.json());
71 | }
72 |
--------------------------------------------------------------------------------
/backend/persistent/datastores/S3.js:
--------------------------------------------------------------------------------
1 | import AWS from 'aws-sdk';
2 | import {parseCSV} from '../../parse.js';
3 |
4 | function createClient(connection) {
5 | AWS.config.update({
6 | accessKeyId: connection.accessKeyId,
7 | secretAccessKey: connection.secretAccessKey
8 | });
9 | return new AWS.S3();
10 | }
11 |
12 |
13 | export function connect(connection) {
14 | const {bucket} = connection;
15 | const client = createClient(connection);
16 | return new Promise((resolve, reject) => {
17 | client.listObjects({Bucket: bucket}, err => {
18 | if (err) {
19 | reject(err);
20 | } else {
21 | resolve();
22 | }
23 | });
24 | });
25 | }
26 |
27 | /*
28 | * Download a (csv) file from S3, parse it
29 | * and return the results.
30 | */
31 | export function query(key, connection) {
32 | const {bucket} = connection;
33 | const client = createClient(connection);
34 | return new Promise((resolve, reject) => {
35 | client.getObject({Bucket: bucket, Key: key}, (err, response) => {
36 | if (err) {
37 | reject(err);
38 | return;
39 | }
40 | const textData = response.Body.toString();
41 | // TODO ^ handle binary files too
42 | parseCSV(textData).then(resolve);
43 | });
44 | });
45 | }
46 |
47 | /*
48 | * List all of the files in an S3 bucket
49 | */
50 | export function files(connection) {
51 | const {bucket} = connection;
52 | const client = createClient(connection);
53 | return new Promise((resolve, reject) => {
54 | client.listObjects({Bucket: bucket}, (err, data) => {
55 | if (err) {
56 | reject(err);
57 | return;
58 | }
59 | resolve(data.Contents);
60 | });
61 | });
62 | }
63 |
--------------------------------------------------------------------------------
/backend/persistent/datastores/ibmdb2.js:
--------------------------------------------------------------------------------
1 | const ibmdb = require('ibm_db');
2 |
3 | import {dissoc} from 'ramda';
4 |
5 | import Logger from '../../logger';
6 | import {parseSQL} from '../../parse';
7 |
8 |
9 | const clients = {};
10 |
11 | function getClient(connection) {
12 | const connectionString = getConnectionString(connection);
13 |
14 | let client = clients[connectionString];
15 | if (!client) {
16 | client = new Promise(function(resolve, reject) {
17 | ibmdb.open(connectionString, function(err, conn) {
18 | if (err) reject(err);
19 | else resolve(conn);
20 | });
21 | });
22 |
23 | clients[connectionString] = client;
24 | }
25 |
26 | return client;
27 | }
28 |
29 | function getConnectionString(connection) {
30 | return `DATABASE=${connection.database};UID=${connection.username};PWD=${connection.password};` +
31 | `HOSTNAME=${connection.host};PORT=${connection.port};PROTOCOL=TCPIP`;
32 | }
33 |
34 | export function connect(connection) {
35 | Logger.log('' +
36 | 'Attempting to authenticate with connection ' +
37 | `${JSON.stringify(dissoc('password', connection), null, 2)} ` +
38 | '(password omitted)'
39 | );
40 |
41 | return getClient(connection);
42 | }
43 |
44 | export function query(queryStmt, connection) {
45 | return getClient(connection)
46 | .then(function(client) {
47 | return new Promise(function(resolve, reject) {
48 | client.query(queryStmt, function(err, rows) {
49 | if (err) reject(err);
50 | else resolve(parseSQL(rows));
51 | });
52 | });
53 | });
54 | }
55 |
56 | const SYSTEM_SCHEMAS = ['SYSCAT', 'SYSIBM', 'SYSIBMADM', 'SYSPUBLIC', 'SYSSTAT', 'SYSTOOLS'];
57 | const WHERE = SYSTEM_SCHEMAS.map(t => `CREATOR <> '${t}'`).join(' AND ');
58 | const QUERY = `SELECT NAME, CREATOR FROM SYSIBM.SYSTABLES WHERE ${WHERE}`;
59 |
60 | export function tables(connection) {
61 | return getClient(connection)
62 | .then(function(client) {
63 | return new Promise(function(resolve, reject) {
64 | client.query(QUERY, function(err, rows) {
65 | if (err) reject(err);
66 | else resolve(rows.map(row => `${row.CREATOR}.${row.NAME}`));
67 | });
68 | });
69 | });
70 | }
71 |
72 | export function schemas(connection) {
73 | return query(
74 | "SELECT TABNAME, COLNAME, TYPENAME FROM syscat.columns WHERE SUBSTR(TABSCHEMA,1,3) != 'SYS'",
75 | connection
76 | );
77 | }
78 |
--------------------------------------------------------------------------------
/backend/persistent/datastores/pool.js:
--------------------------------------------------------------------------------
1 | export default class Pool {
2 | /**
3 | * Pool keeps a list of clients indexed by connection objects
4 | *
5 | * @param {function} newClient Function that takes a connection object and creates a new client
6 | * @param {function} sameConnection Function that returns whether two connection objects are the same
7 | */
8 | constructor(newClient, sameConnection) {
9 | this.newClient = newClient;
10 | this.sameConnection = sameConnection;
11 | this._pool = [];
12 | }
13 |
14 | /**
15 | * Get client indexed by connection (if no client found, a new client is created using newClient)
16 | * @param {object} connection Connection object
17 | * @returns {*} client for connection
18 | */
19 | getClient(connection) {
20 | for (let i = this._pool.length - 1; i >= 0; i--) {
21 | if (this.sameConnection(connection, this._pool[i][0])) {
22 | return this._pool[i][1];
23 | }
24 | }
25 |
26 | const client = this.newClient(connection);
27 | this._pool.push([connection, client]);
28 | return client;
29 | }
30 |
31 | /**
32 | * Remove connection from pool
33 | * @param {object} connection Connection object
34 | * @returns {*} removed client (or undefined, if no client was found)
35 | */
36 | remove(connection) {
37 | for (let i = this._pool.length - 1; i >= 0; i--) {
38 | if (this.sameConnection(connection, this._pool[i][0])) {
39 | return this._pool.splice(i, 1)[0][1];
40 | }
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/backend/preload.js:
--------------------------------------------------------------------------------
1 | // this file is preloaded by electron in the renderer process
2 | // (it has access to electron's API, even when nodeIntegration has been disabled)
3 |
4 | const {ipcRenderer, remote} = require('electron');
5 |
6 | const propertyOptions = {
7 | configurable: false,
8 | enumerable: false,
9 | writable: false
10 | };
11 |
12 | process.once('loaded', () => {
13 | const $falcon = {};
14 |
15 | Object.defineProperty(window, '$falcon', {
16 | value: $falcon,
17 | ...propertyOptions
18 | });
19 |
20 | Object.defineProperty($falcon, 'showOpenDialog', {
21 | value: remote.dialog.showOpenDialog,
22 | ...propertyOptions
23 | });
24 |
25 | let onUsername;
26 | let receivedUsernameBeforeCallback;
27 | let username;
28 | function sendUsername(event, user) {
29 | try {
30 | if (onUsername) {
31 | receivedUsernameBeforeCallback = false;
32 | onUsername(event, user);
33 | } else {
34 | receivedUsernameBeforeCallback = true;
35 | username = user;
36 | }
37 | } catch (error) {
38 | console.error(error); // eslint-disable-line
39 | }
40 | }
41 |
42 | ipcRenderer.on('username', sendUsername);
43 |
44 | Object.defineProperty($falcon, 'setUsernameListener', {
45 | value: (value) => {
46 | onUsername = value;
47 |
48 | if (receivedUsernameBeforeCallback) {
49 | sendUsername(null, username);
50 | }
51 | },
52 | ...propertyOptions
53 | });
54 | });
55 |
--------------------------------------------------------------------------------
/backend/utils/authUtils.js:
--------------------------------------------------------------------------------
1 | import uuidv4 from 'uuid/v4';
2 | import {getSetting, saveSetting} from '../settings';
3 |
4 | export function generateAndSaveAccessToken() {
5 | const currentTime = Date.now();
6 |
7 | if (getSetting('ACCESS_TOKEN_EXPIRY') < currentTime) {
8 | const accessToken = uuidv4();
9 | saveSetting('ACCESS_TOKEN', accessToken);
10 |
11 | // ACCESS_TOKEN_AGE is expressed in seconds, so we convert it to milliseconds:
12 | saveSetting('ACCESS_TOKEN_EXPIRY', currentTime + (getSetting('ACCESS_TOKEN_AGE') * 1000));
13 | return accessToken;
14 | }
15 |
16 | return getSetting('ACCESS_TOKEN');
17 | }
18 |
--------------------------------------------------------------------------------
/backend/utils/cronUtils.js:
--------------------------------------------------------------------------------
1 | const MAX_CRON_MINUTE = 59;
2 |
3 | export function mapRefreshToCron (refreshInterval) {
4 | const now = new Date();
5 |
6 | // try to intelligently select interval
7 | if (refreshInterval <= (60 * (1 + 5)) / 2) {
8 | // case: refreshInterval is closer to 1 minute than to 5 minutes
9 | return '* * * * *';
10 | } else if (refreshInterval <= (60 * (5 + 60)) / 2) {
11 | // case: refreshInterval is closer to 5 minutes than to 1 hour
12 | return `${now.getSeconds()} ${computeMinutes(now)} * * * *`;
13 | } else if (refreshInterval <= (60 * 60 * (1 + 24)) / 2) {
14 | // case: refreshInterval is closer to 1 hour than to 1 day
15 | return `${now.getMinutes()} * * * *`;
16 | } else if (refreshInterval <= (24 * 60 * 60 * (1 + 7)) / 2) {
17 | // case: refreshInterval is closer to 1 day than to 1 week
18 | return `${now.getMinutes()} ${now.getHours()} * * *`;
19 | }
20 |
21 | // otherwise, default to once a week
22 | return `${now.getMinutes()} ${now.getHours()} * * ${now.getDay()}`;
23 | }
24 |
25 | export function mapCronToRefresh (cronInterval) {
26 | const DEFAULT_INTERVAL = 60 * 60 * 24 * 7; // default to weekly
27 |
28 | if (!cronInterval) {
29 | return DEFAULT_INTERVAL;
30 | }
31 |
32 | if (cronInterval === '* * * * *') {
33 | return 60;
34 | } else if (cronInterval === '*/5 * * * *') {
35 | return 60 * 5;
36 | } else if (cronInterval.match(/\S+? \* \* \* \*/)) {
37 | return 60 * 60;
38 | } else if (cronInterval.match(/\S+? \S+? \* \* \*/)) {
39 | return 60 * 60 * 24;
40 | }
41 |
42 | return DEFAULT_INTERVAL;
43 | }
44 |
45 | function computeMinutes (now) {
46 | let currMinute = now.getMinutes() % 5; // start at 5 min offset
47 | const minutes = [];
48 |
49 | while (currMinute < MAX_CRON_MINUTE) {
50 | minutes.push(currMinute);
51 | currMinute += 5;
52 | }
53 |
54 | return minutes.join(',');
55 | }
--------------------------------------------------------------------------------
/backend/utils/gridUtils.js:
--------------------------------------------------------------------------------
1 | export function extractOrderedUids (grid) {
2 | try {
3 | return Object.keys(grid.cols)
4 | .map(key => grid.cols[key])
5 | .sort((a, b) => a.order - b.order)
6 | .map(col => col.uid);
7 | } catch (e) {
8 | return null;
9 | }
10 | }
--------------------------------------------------------------------------------
/backend/utils/homeFiles.js:
--------------------------------------------------------------------------------
1 | import {getSetting} from '../settings';
2 | import mkdirp from 'mkdirp';
3 |
4 | export function createStoragePath () {
5 | mkdirp.sync(getSetting('STORAGE_PATH'));
6 | }
7 |
--------------------------------------------------------------------------------
/backend/utils/persistenceUtils.js:
--------------------------------------------------------------------------------
1 | import {reject} from 'ramda';
2 |
3 | // prevent 'yamljs' from coerecing undefined keys into null
4 | export function stripUndefinedKeys (obj) {
5 | return reject(isUndefined, obj);
6 | }
7 |
8 | function isUndefined (val) {
9 | return typeof val === 'undefined';
10 | }
--------------------------------------------------------------------------------
/circle.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | jobs:
3 | build:
4 | docker:
5 | - image: circleci/node:8-browsers
6 | - image: quay.io/plotly/falcon-test-spark
7 | - image: quay.io/plotly/falcon-test-db2
8 | - image: quay.io/plotly/falcon-test-clickhouse
9 | environment:
10 | PATH: "/usr/local/opt/curl/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/home/circleci/project/node_modules/.bin"
11 | working_directory: /home/circleci/falcon
12 | steps:
13 | - restore_cache:
14 | key: v1-source-{{ .Branch }}-{{ .Revision }}
15 | keys:
16 | - v1-source-{{ .Branch }}-
17 | - v1-source-
18 | - checkout
19 | - save_cache:
20 | key: v1-source-{{ .Branch }}-{{ .Revision }}
21 | paths:
22 | - ".git"
23 | - restore_cache:
24 | key: v2-yarn-{{ checksum "yarn.lock" }}
25 | keys:
26 | - v2-yarn-
27 | - run: node --version
28 | - run: yarn --version
29 | - run: yarn install
30 | - run: yarn run rebuild:modules:electron
31 | - save_cache:
32 | key: v2-yarn-{{ checksum "yarn.lock" }}
33 | paths:
34 | - node_modules
35 | - run: yarn run build
36 | - run: yarn run test
37 | - run: yarn run pack
38 |
39 | # create artifacts/ and move installer from release/falcon-sql-client-vM.m.p.tar.gz to artifacts/linux-falcon-vM.m.p.tar.gz
40 | - run: mkdir artifacts
41 | - run: for f in release/falcon-sql-client-*.tar.gz ; do mv $f artifacts/linux-falcon-v${f#release/falcon-sql-client-}; done
42 |
43 | - store_artifacts:
44 | path: artifacts
45 |
--------------------------------------------------------------------------------
/scripts/fix-module-ibmdb.js:
--------------------------------------------------------------------------------
1 | const os = require('os');
2 |
3 | function execSync(command) {
4 | console.log('>>', command); // eslint-disable-line no-console
5 | return require('child_process').execSync(command, {stdio: 'inherit'});
6 | }
7 |
8 | // Workaround for https://github.com/ibmdb/node-ibm_db/issues/329
9 | if (os.platform() === 'darwin') {
10 | execSync([
11 | 'install_name_tool -change',
12 | '`pwd`/node_modules/ibm_db/installer/clidriver/lib/libdb2.dylib',
13 | '@loader_path/../../installer/clidriver/lib/libdb2.dylib',
14 | 'node_modules/ibm_db/build/Release/odbc_bindings.node'
15 | ].join(' '));
16 | }
17 |
--------------------------------------------------------------------------------
/scripts/rebuild-modules.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | const electronEnv = (process.argv[2] === '--electron');
4 |
5 | function execSync(command) {
6 | console.log('>>', command); // eslint-disable-line no-console
7 | return require('child_process').execSync(command, {stdio: 'inherit'});
8 | }
9 |
10 | if (electronEnv) {
11 | const localBin = require('child_process').execSync('yarn bin').toString().trim();
12 | const installAppDeps = path.join(localBin, 'electron-builder install-app-deps');
13 | execSync(installAppDeps);
14 | } else {
15 | execSync('yarn install --force');
16 | }
17 |
--------------------------------------------------------------------------------
/shared/constants.js:
--------------------------------------------------------------------------------
1 | export const EXE_STATUS = {
2 | running: 'running',
3 | ok: 'ok',
4 | failed: 'failed'
5 | };
--------------------------------------------------------------------------------
/static/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 | #ffffff
--------------------------------------------------------------------------------
/static/css-vendor/Resizer.css:
--------------------------------------------------------------------------------
1 | .Resizer {
2 | background: #000;
3 | opacity: .2;
4 | z-index: 1;
5 | -moz-box-sizing: border-box;
6 | -webkit-box-sizing: border-box;
7 | box-sizing: border-box;
8 | -moz-background-clip: padding;
9 | -webkit-background-clip: padding;
10 | background-clip: padding-box;
11 | }
12 |
13 | .Resizer:hover {
14 | -webkit-transition: all 2s ease;
15 | transition: all 2s ease;
16 | }
17 |
18 | .Resizer.horizontal {
19 | height: 11px;
20 | margin: -5px 0;
21 | border-top: 5px solid rgba(255, 255, 255, 0);
22 | border-bottom: 5px solid rgba(255, 255, 255, 0);
23 | cursor: row-resize;
24 | width: 100%;
25 | }
26 |
27 | .Resizer.horizontal:hover {
28 | border-top: 5px solid rgba(0, 0, 0, 0.5);
29 | border-bottom: 5px solid rgba(0, 0, 0, 0.5);
30 | }
31 |
32 | .Resizer.vertical {
33 | width: 11px;
34 | margin: 0 -5px;
35 | border-left: 5px solid rgba(255, 255, 255, 0);
36 | border-right: 5px solid rgba(255, 255, 255, 0);
37 | cursor: col-resize;
38 | }
39 |
40 | .Resizer.vertical:hover {
41 | border-left: 5px solid rgba(0, 0, 0, 0.5);
42 | border-right: 5px solid rgba(0, 0, 0, 0.5);
43 | }
44 | .Resizer.disabled {
45 | cursor: not-allowed;
46 | }
47 | .Resizer.disabled:hover {
48 | border-color: transparent;
49 | }
--------------------------------------------------------------------------------
/static/css-vendor/Treeview.css:
--------------------------------------------------------------------------------
1 | /* the tree node's style */
2 | .tree-view {
3 | overflow-y: hidden;
4 | overflow-x: hidden;
5 | font-size: 13px;
6 | padding: 2px 0;
7 | font-family: 'Ubuntu', courier, monospace;
8 | }
9 |
10 | .tree-view_item {
11 | /* immediate child of .tree-view, for styling convenience */
12 | }
13 |
14 | /* style for the children nodes container */
15 | .tree-view_children {
16 | margin-left: 16px;
17 | }
18 |
19 | .tree-view_children-collapsed {
20 | height: 0px;
21 | }
22 |
23 | .tree-view_arrow {
24 | cursor: pointer;
25 | margin-right: 6px;
26 | display: inline-block;
27 | -webkit-user-select: none;
28 | -moz-user-select: none;
29 | -ms-user-select: none;
30 | user-select: none;
31 | display: inline;
32 | color: #a2b1c6;
33 | }
34 |
35 | .tree-view_arrow:hover {
36 | color: #119DFF;
37 | }
38 |
39 | .tree-view_arrow:after {
40 | content: '▾';
41 | font-size: 14px;
42 | }
43 |
44 | .tree-view_arrow-collapsed:after {
45 | content: '►';
46 | font-size: 8px;
47 | }
48 |
49 | /* rotate the triangle to close it */
50 | .tree-view_arrow-collapsed {
51 | -webkit-transform: rotate(-90deg);
52 | -moz-transform: rotate(-90deg);
53 | -ms-transform: rotate(-90deg);
54 | transform: rotate(-90deg);
55 | }
56 |
57 | .tree-view .info {
58 | padding: 0;
59 | font-size: 13px;
60 | overflow: hidden;
61 | white-space: nowrap;
62 | padding: 2px 0;
63 | }
64 |
65 | .tree-view .info code {
66 | font-size: 11px;
67 | padding: 1px 6px;
68 | background: #EBF0F8;
69 | color: black;
70 | border-radius: 2px;
71 | border: 1px solid #C8D4E3;
72 | }
73 |
74 | .tree-view-container {
75 | height: 100%;
76 | background: rgb(251, 252, 253);
77 | border-radius: 0 0 0 6px;
78 | overflow: hidden;
79 | }
80 |
81 | .loading {
82 | padding: 10px;
83 | }
84 |
85 | .loading:after {
86 | overflow: hidden;
87 | display: inline-block;
88 | vertical-align: bottom;
89 | -webkit-animation: ellipsis steps(4,end) 900ms infinite;
90 | animation: ellipsis steps(4,end) 900ms infinite;
91 | content: "\2026"; /* ascii code for the ellipsis character */
92 | width: 0px;
93 | }
94 |
95 | @keyframes ellipsis {
96 | to {
97 | width: 1.25em;
98 | }
99 | }
100 |
101 | @-webkit-keyframes ellipsis {
102 | to {
103 | width: 1.25em;
104 | }
105 | }
--------------------------------------------------------------------------------
/static/css-vendor/react-select-custom.css:
--------------------------------------------------------------------------------
1 | .Select-menu-outer {
2 | z-index: 999 !important;
3 | }
4 |
--------------------------------------------------------------------------------
/static/css-vendor/react-tabs.css:
--------------------------------------------------------------------------------
1 | .react-tabs__tab-list {
2 | padding: 5px 0;
3 | margin: 0;
4 | text-transform: uppercase;
5 | font-family: 'Dosis';
6 | color: #506784;
7 | font-size: 18px;
8 | border-bottom: 1px solid #c8dfe3;
9 | background: #fff;
10 | }
11 |
12 | .react-tabs__tab {
13 | display: inline-block;
14 | border-bottom: none;
15 | position: relative;
16 | list-style: none;
17 | padding: 6px 20px;
18 | cursor: pointer;
19 | }
20 |
21 | .react-tabs__tab--selected {
22 | background: #fff;
23 | border-radius: 0;
24 | color: #119dff;
25 | }
26 |
27 | .react-tabs__tab-panel {
28 | display: none;
29 | }
30 |
31 | .react-tabs__tab-panel--selected {
32 | display: block;
33 | }
34 |
35 | .react-tabs__tab--disabled {
36 | color: GrayText;
37 | cursor: default;
38 | }
39 |
40 | .react-tabs__tab:focus {
41 | outline: none;
42 | }
43 |
44 | .react-tabs__tab:focus:after {
45 | content: "";
46 | position: absolute;
47 | height: 5px;
48 | left: -4px;
49 | right: -4px;
50 | bottom: -5px;
51 | background: #fff;
52 | }
53 |
54 | .big-whitespace-tab {
55 | padding: 50px 200px;
56 | }
57 |
58 | .react-tabs__tab-list {
59 | -webkit-animation: fadein 2s; /* Safari, Chrome and Opera > 12.1 */
60 | -moz-animation: fadein 2s; /* Firefox < 16 */
61 | -ms-animation: fadein 2s; /* Internet Explorer */
62 | -o-animation: fadein 2s; /* Opera < 12.1 */
63 | animation: fadein 2s;
64 | }
65 |
66 | @keyframes fadein {
67 | from { opacity: 0; }
68 | to { opacity: 1; }
69 | }
70 |
71 | /* Firefox < 16 */
72 | @-moz-keyframes fadein {
73 | from { opacity: 0; }
74 | to { opacity: 1; }
75 | }
76 |
77 | /* Safari, Chrome and Opera > 12.1 */
78 | @-webkit-keyframes fadein {
79 | from { opacity: 0; }
80 | to { opacity: 1; }
81 | }
82 |
83 | /* Internet Explorer */
84 | @-ms-keyframes fadein {
85 | from { opacity: 0; }
86 | to { opacity: 1; }
87 | }
88 |
89 | /* Opera < 12.1 */
90 | @-o-keyframes fadein {
91 | from { opacity: 0; }
92 | to { opacity: 1; }
93 | }
94 |
95 |
--------------------------------------------------------------------------------
/static/css-vendor/show-hint.css:
--------------------------------------------------------------------------------
1 | .CodeMirror-hints {
2 | position: absolute;
3 | z-index: 10;
4 | overflow: hidden;
5 | list-style: none;
6 |
7 | margin: 0;
8 | padding: 2px;
9 |
10 | -webkit-box-shadow: 2px 3px 5px rgba(0,0,0,.2);
11 | box-shadow: 2px 3px 5px rgba(0,0,0,.2);
12 | border-radius: 3px;
13 | border: 1px solid silver;
14 |
15 | background: white;
16 | font-size: 90%;
17 | font-family: monospace;
18 |
19 | max-height: 20em;
20 | overflow-y: auto;
21 | }
22 |
23 | .CodeMirror-hint {
24 | margin: 0;
25 | padding: 0 4px;
26 | border-radius: 2px;
27 | white-space: pre;
28 | color: black;
29 | cursor: pointer;
30 | }
31 |
32 | li.CodeMirror-hint-active {
33 | background: #08f;
34 | color: white;
35 | }
--------------------------------------------------------------------------------
/static/css/Configuration.css:
--------------------------------------------------------------------------------
1 | .fullApp {
2 | }
3 |
4 | .header {
5 | vertical-align: top;
6 | }
7 |
8 | .logoAndTitle {
9 | display: inline-block;
10 | }
11 |
12 | .plotlyLogo {
13 | width: 100px;
14 | display: inline-block;
15 | }
16 |
17 | .applicationTitle {
18 | margin-top: 25px;
19 | display: inline-block;
20 | vertical-align: top;
21 | color: #096CB3;
22 | }
23 |
24 | .userName {
25 | color: #096CB3;
26 | }
27 |
28 | .supportLinksContainer {
29 | float: right;
30 | }
31 |
32 | .externalLinkContainer {
33 | display: inline-block;
34 | }
35 |
36 | .externalLink {
37 | color: #119DFF;
38 | cursor: pointer;
39 | text-decoration: underline;
40 | cursor: pointer;
41 | padding: 10px;
42 | }
43 |
44 | .settings {
45 | background-color: white;
46 | padding-top: 20px;
47 | margin: 0px 20px 0px 20px;
48 | }
49 |
50 | .onPremForm {
51 | text-align: center;
52 | }
53 |
54 | .onPremDomainInput {
55 | text-align: center;
56 | }
57 |
58 | .addOnPremLink {
59 | cursor: pointer;
60 | text-align: center;
61 | }
62 |
--------------------------------------------------------------------------------
/static/css/ConnectButton.css:
--------------------------------------------------------------------------------
1 | .connectButtonContainer {
2 | margin-top: 30px;
3 | border-top: thin #c8dfe3 dashed;
4 | padding-top: 30px;
5 | }
6 |
7 | .errorMessage {
8 | color: red;
9 | height: 30px;
10 | padding-top: 10px;
11 | }
12 |
13 | .footer {
14 | padding-top: 10px;
15 | }
16 |
--------------------------------------------------------------------------------
/static/css/DialectSelector.css:
--------------------------------------------------------------------------------
1 | .logoImage {
2 | height: auto;
3 | max-height: 75%;
4 | max-width: 100%;
5 | width: auto;
6 | }
7 |
--------------------------------------------------------------------------------
/static/css/Link.css:
--------------------------------------------------------------------------------
1 | .a {
2 | color: blue;
3 | cursor: pointer;
4 | text-decoration: underline;
5 | }
6 |
7 | .a:hover {
8 | color: #0D76BF;
9 | }
10 |
--------------------------------------------------------------------------------
/static/css/Logger.css:
--------------------------------------------------------------------------------
1 | .renderLogs {
2 | max-height: 300px;
3 | overflow-y: scroll;
4 | }
5 |
--------------------------------------------------------------------------------
/static/css/Preview.css:
--------------------------------------------------------------------------------
1 | table {
2 | border-collapse: collapse;
3 | border-spacing: 0;
4 | }
5 |
6 | th, td {
7 | border: 1px solid #828282;
8 | color: #828282;
9 | font-size: 0.875em;
10 | padding: 0.3125rem 0.625rem;
11 | text-align: left;
12 | }
13 |
14 | .previewContainer {
15 | border-radius: 3px;
16 | margin: 0 25px 25px 25px;
17 | overflow: auto;
18 | }
19 |
20 | .runButton {
21 | bottom: 18px;
22 | position: absolute;
23 | right: 18px;
24 | width: 70px;
25 | z-index: 10;
26 | }
27 |
28 | .errorStatus pre {
29 | background: none;
30 | border: none;
31 | border-bottom: 1px solid #ebf0f8;
32 | color: rgb(211, 96, 70);
33 | font-family: 'Ubuntu Mono';
34 | font-size: 12px;
35 | margin: 0 0 20px 0;
36 | max-height: 150px;
37 | overflow-x: auto;
38 | overflow-y: auto;
39 | padding: 20px;
40 | }
41 |
42 | .successMsg p {
43 | background: none;
44 | border: none;
45 | color: #00cc96;
46 | font-family: 'Ubuntu Mono';
47 | font-size: 14px;
48 | margin: 0;
49 | padding: 20px;
50 | }
51 |
52 | .container-title {
53 | font-size: 12px;
54 | margin-bottom: 8px;
55 | }
56 |
57 | .export-options-group {
58 | margin: 8px 0 20px;
59 | padding: 20px;
60 | background: #f3f6fa;
61 | border: 1px solid #ebf0f8;
62 | }
63 |
--------------------------------------------------------------------------------
/static/css/Settings.css:
--------------------------------------------------------------------------------
1 | .openTab {
2 | background: rgb(251, 252, 253);
3 | border-bottom: 1px solid #c8dfe3;
4 | border-left: 1px solid #c8dfe3;
5 | border-radius: 0 0 6px 6px;
6 | border-right: 1px solid #c8dfe3;
7 | }
8 |
9 | .configurationContainer {
10 | font-family: 'Ubuntu';
11 | text-align: center;
12 | width: 100%;
13 | }
14 |
15 | .disabledSection {
16 | opacity: 0.4;
17 | pointer-events: none;
18 | }
19 |
20 | .info {
21 | font-size: 14px;
22 | padding: 25px;
23 | }
24 |
25 | .dialectSelector {
26 | margin-top: 32px;
27 | }
28 |
29 | .dialectSelector .Select {
30 | width: 56%;
31 | height: 40px;
32 | margin-left: 20px;
33 | font-size: 14px;
34 | font-weight: 300;
35 | text-align: left;
36 | }
37 |
38 | .editButtonContainer {
39 | margin: 20px 0;
40 | }
41 |
42 | .url {
43 | text-decoration: underline;
44 | }
45 |
--------------------------------------------------------------------------------
/static/css/TableDropdown.css:
--------------------------------------------------------------------------------
1 | .dropdown {
2 | text-transform: capitalize;
3 | cursor: pointer;
4 | width: 80%;
5 | padding-bottom: 10px;
6 | padding-left: 25px;
7 | }
8 |
--------------------------------------------------------------------------------
/static/css/Tabs.css:
--------------------------------------------------------------------------------
1 |
2 | .tabManagerWrapper {
3 | text-align: left;
4 | width: 100%;
5 | white-space: nowrap;
6 | overflow-x: auto;
7 | overflow-y: hidden;
8 | }
9 |
10 | .tabManagerContainer {
11 | margin-left: auto;
12 | margin-right: auto;
13 | text-align: left;
14 | }
15 |
16 |
17 | .tabAddWrapper {
18 | background-color: white;
19 | display: inline-block;
20 | border-width: 1px;
21 | border-color: #c8dfe3;
22 | border-style: solid;
23 | vertical-align: middle;
24 | height: 36px;
25 | width: 36px;
26 | border-top-width: 0;
27 | border-bottom-width: 3px;
28 | }
29 |
30 | .tabWrapper {
31 | display: inline-block;
32 | cursor: pointer;
33 | border-left: 1px solid #c8dfe3;
34 | border-bottom: 1px solid #c8dfe3;
35 | border-top: 1px solid #c8dfe3;
36 | background-color: white;
37 | color: #687ba6;
38 | padding: 5px 10px 5px 10px;
39 | vertical-align: middle;
40 | max-width: 300px;
41 | white-space: nowrap;
42 | overflow-x: hidden;
43 | text-overflow: ellipsis;
44 | border-top: 3px solid #c8dfe3;
45 | }
46 |
47 | .tabLogo {
48 | height: 20px;
49 | display: inline;
50 | vertical-align: middle;
51 | padding-right: 5px;
52 | }
53 |
54 | .tabIdentifier {
55 | margin: 0px 20px 0px 0px;
56 | display: inline;
57 | vertical-align: middle;
58 | }
59 |
60 | .tabDelete {
61 | width: 12px;
62 | padding-right: 5px;
63 | vertical-align: middle;
64 | }
65 |
66 | .tabAdd {
67 | width: 20px;
68 | padding: 8px;
69 | }
70 |
71 |
72 | .tabWrapper:hover, tabWrapper:focus{
73 | cursor: pointer;
74 | }
75 |
76 | .tabAdd:hover, tab:focus{
77 | cursor: pointer;
78 | }
79 |
80 | .tabDelete:hover, tab:focus{
81 | cursor: pointer;
82 | }
83 |
84 | .tabWrapperSelected {
85 | border-bottom: 1px solid #fff;
86 | border-top: 3px solid #119dff;
87 | }
88 |
89 | .tabAddText {
90 | display: inline-block;;
91 | padding: 10px 20px 10px 20px;
92 | color: #8BB5FE;
93 | }
94 |
95 | /* this flips the scroll y bar from bottom to up of the tab tab */
96 | .Flipped, .Flipped .tabWrapper, .Flipped .tabAddText
97 | {
98 | transform:rotateX(180deg);
99 | -ms-transform:rotateX(180deg); /* IE 9 */
100 | -webkit-transform:rotateX(180deg); /* Safari and Chrome */
101 | }
102 |
--------------------------------------------------------------------------------
/static/css/UserConnections.css:
--------------------------------------------------------------------------------
1 | label {
2 | text-align: right;
3 | float: left;
4 | width: 35%;
5 | line-height: 40px;
6 | font-size: 16px;
7 | }
8 |
9 | .wrapInput {
10 | width: 65%;
11 | float: right;
12 | }
13 |
14 | input {
15 | border: none;
16 | box-sizing: border-box;
17 | border-bottom-width: 3px;
18 | border-bottom-color: #dfe8f3;
19 | border-bottom-style: solid;
20 | color: #6D6D6D;
21 | font-weight: 300;
22 | width: 56%;
23 | margin-left: 20px;
24 | float: left;
25 | font-size: 14px;
26 | height: 40px;
27 | padding: 10px;
28 | }
29 |
30 | input:hover,input:focus {
31 | border-bottom-color: #119dff;
32 | outline: none;
33 | }
34 |
35 | .inputContainer {
36 | clear: both;
37 | overflow: hidden;
38 | padding: 5px 0;
39 | }
40 |
41 | .description {
42 | font-size: 12px;
43 | text-align: left;
44 | margin-left: calc(35% + 20px);
45 | max-width: 35%;
46 | }
47 |
48 | .documentationLink {
49 | display: inline-block !important;
50 | padding: 5px;
51 | text-transform: capitalize;
52 | font-size: 18px;
53 | font-weight: 100;
54 | margin: 10px 40px 0px 40px;
55 | text-align: left;
56 | height: 30px;
57 | width: 80%;
58 | }
59 |
60 | .sampleCredentials {
61 | float: right;
62 | margin-right: 10px;
63 | }
--------------------------------------------------------------------------------
/static/css/fixed-data-table-custom.css:
--------------------------------------------------------------------------------
1 | .public_fixedDataTable_main, .fixedDataTableLayout_main public_fixedDataTable_main {
2 | font-size: 12px !important;
3 | border-color: #c8d4e3;
4 | }
5 |
6 | .public_fixedDataTable_header, .public_fixedDataTable_header .public_fixedDataTableCell_main {
7 | background-image: none;
8 | background: #f3f6fa;
9 | }
--------------------------------------------------------------------------------
/static/fonts/dosis-v7-latin-ext_latin-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/static/fonts/dosis-v7-latin-ext_latin-regular.woff2
--------------------------------------------------------------------------------
/static/fonts/open-sans-v15-vietnamese_greek_latin-ext_greek-ext_cyrillic-ext_cyrillic_latin-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/static/fonts/open-sans-v15-vietnamese_greek_latin-ext_greek-ext_cyrillic-ext_cyrillic_latin-regular.woff2
--------------------------------------------------------------------------------
/static/fonts/ubuntu-mono-v7-greek_latin-ext_greek-ext_cyrillic-ext_cyrillic_latin-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/static/fonts/ubuntu-mono-v7-greek_latin-ext_greek-ext_cyrillic-ext_cyrillic_latin-regular.woff2
--------------------------------------------------------------------------------
/static/fonts/ubuntu-v11-greek_latin-ext_greek-ext_cyrillic-ext_cyrillic_latin-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/static/fonts/ubuntu-v11-greek_latin-ext_greek-ext_cyrillic-ext_cyrillic_latin-regular.woff2
--------------------------------------------------------------------------------
/static/images/android-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/static/images/android-icon-144x144.png
--------------------------------------------------------------------------------
/static/images/android-icon-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/static/images/android-icon-192x192.png
--------------------------------------------------------------------------------
/static/images/android-icon-36x36.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/static/images/android-icon-36x36.png
--------------------------------------------------------------------------------
/static/images/android-icon-48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/static/images/android-icon-48x48.png
--------------------------------------------------------------------------------
/static/images/android-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/static/images/android-icon-72x72.png
--------------------------------------------------------------------------------
/static/images/android-icon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/static/images/android-icon-96x96.png
--------------------------------------------------------------------------------
/static/images/apple-icon-114x114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/static/images/apple-icon-114x114.png
--------------------------------------------------------------------------------
/static/images/apple-icon-120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/static/images/apple-icon-120x120.png
--------------------------------------------------------------------------------
/static/images/apple-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/static/images/apple-icon-144x144.png
--------------------------------------------------------------------------------
/static/images/apple-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/static/images/apple-icon-152x152.png
--------------------------------------------------------------------------------
/static/images/apple-icon-180x180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/static/images/apple-icon-180x180.png
--------------------------------------------------------------------------------
/static/images/apple-icon-57x57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/static/images/apple-icon-57x57.png
--------------------------------------------------------------------------------
/static/images/apple-icon-60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/static/images/apple-icon-60x60.png
--------------------------------------------------------------------------------
/static/images/apple-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/static/images/apple-icon-72x72.png
--------------------------------------------------------------------------------
/static/images/apple-icon-76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/static/images/apple-icon-76x76.png
--------------------------------------------------------------------------------
/static/images/apple-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/static/images/apple-icon-precomposed.png
--------------------------------------------------------------------------------
/static/images/apple-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/static/images/apple-icon.png
--------------------------------------------------------------------------------
/static/images/falcon-logo-by-plotly-stripe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/static/images/falcon-logo-by-plotly-stripe.png
--------------------------------------------------------------------------------
/static/images/falcon_hero.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/static/images/falcon_hero.gif
--------------------------------------------------------------------------------
/static/images/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/static/images/favicon-16x16.png
--------------------------------------------------------------------------------
/static/images/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/static/images/favicon-32x32.png
--------------------------------------------------------------------------------
/static/images/favicon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/static/images/favicon-96x96.png
--------------------------------------------------------------------------------
/static/images/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/static/images/favicon.ico
--------------------------------------------------------------------------------
/static/images/ms-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/static/images/ms-icon-144x144.png
--------------------------------------------------------------------------------
/static/images/ms-icon-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/static/images/ms-icon-150x150.png
--------------------------------------------------------------------------------
/static/images/ms-icon-310x310.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/static/images/ms-icon-310x310.png
--------------------------------------------------------------------------------
/static/images/ms-icon-70x70.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/static/images/ms-icon-70x70.png
--------------------------------------------------------------------------------
/test/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "mocha": true
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/test/app/components/Settings/DialectSelector/DialectSelector.test.js:
--------------------------------------------------------------------------------
1 | jest.unmock('../../../../../app/components/Settings/DialectSelector/DialectSelector.react.js');
2 | jest.unmock('../../../../../app/constants/constants.js');
3 |
4 | import DialectSelector from '../../../../../app/components/Settings/DialectSelector/DialectSelector.react.js';
5 | import {DIALECTS} from '../../../../../app/constants/constants.js';
6 | import React from 'react';
7 | import Select from 'react-select';
8 | import {mount, configure} from 'enzyme';
9 | import Adapter from 'enzyme-adapter-react-16';
10 |
11 | describe('Dialog Selector Test', () => {
12 | beforeAll(() => {
13 | configure({adapter: new Adapter()});
14 | });
15 |
16 | it('should verify Dialect Selector created without dialect', () => {
17 | const updateConnection = function() {};
18 | const connectionObject = {};
19 |
20 | const selector = mount(
21 |
22 | );
23 |
24 | // Number of Logos should match number of images
25 | expect(selector.find(Select).prop('options').length).toEqual(Object.keys(DIALECTS).length);
26 |
27 | // Dialect not selected so should not be found
28 | expect(selector.find('.Select-option .is-selected').length).toBe(0);
29 | });
30 |
31 | it('should verify Dialect Selector created with dialect', () => {
32 | const updateConnection = function() {};
33 | const connectionObject = {
34 | dialect: 'mysql'
35 | };
36 |
37 | const selector = mount(
38 |
39 | );
40 |
41 | // open selector
42 | selector.find('.Select-control').simulate('keyDown', {keyCode: 40});
43 |
44 | expect(
45 | selector
46 | .find('.Select-option .is-selected')
47 | .first()
48 | .text()
49 | ).toBe(connectionObject.dialect);
50 | });
51 | });
52 |
--------------------------------------------------------------------------------
/test/app/components/Settings/Preview/sql-table.test.jsx:
--------------------------------------------------------------------------------
1 | jest.unmock('../../../../../app/components/Settings/Preview/sql-table.jsx');
2 | import SQLTable from '../../../../../app/components/Settings/Preview/sql-table.jsx';
3 | import React from 'react';
4 | import {configure, mount} from 'enzyme';
5 | import Adapter from 'enzyme-adapter-react-16';
6 |
7 | describe('SQLTable', () => {
8 | let sqlTable, columnNames, rows;
9 |
10 | beforeAll(() => {
11 | configure({adapter: new Adapter()});
12 |
13 | columnNames = ['col 1', 'col 2', 'col 3', 'col 4'];
14 |
15 | rows = [1, 2, 3, 4, 5].map(row =>
16 | [1, 2, 3, 4].map(col =>
17 | `(${row},${col})`
18 | )
19 | );
20 |
21 | sqlTable = mount(
22 |
26 | );
27 | });
28 |
29 | it('honors props', () => {
30 | const tableHeaderCells = sqlTable.find('.react-grid-HeaderCell');
31 |
32 | expect(tableHeaderCells.length).toBe(columnNames.length);
33 |
34 | tableHeaderCells.forEach((headerCell, index) => {
35 | const value = headerCell.find('.widget-HeaderCell__value').first();
36 | expect(value.text()).toBe(columnNames[index]);
37 | });
38 |
39 | const tableRows = sqlTable.find('.react-grid-Row');
40 |
41 | expect(tableRows.length).toBe(rows.length);
42 | tableRows.forEach((tableRow, i) => {
43 | const tableRowCells = tableRow.find('.react-grid-Cell__value');
44 | tableRowCells.forEach((cell, j) => {
45 | const value = cell.childAt(0).childAt(0).childAt(0);
46 | expect(value.text()).toBe(rows[i][j]);
47 | });
48 | });
49 | });
50 |
51 | it('can toggle row filters', () => {
52 | const toggle = sqlTable.find('a').first();
53 |
54 | // before clicking
55 | expect(sqlTable.find('.react-grid-HeaderRow').length).toBe(1);
56 |
57 | toggle.simulate('click');
58 |
59 | // after clicking
60 | expect(sqlTable.find('.react-grid-HeaderRow').length).toBe(2);
61 | });
62 | });
63 |
--------------------------------------------------------------------------------
/test/app/components/Settings/scheduler/color-picker.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {mount, configure} from 'enzyme';
3 | import {ChromePicker} from 'react-color';
4 |
5 | import Adapter from 'enzyme-adapter-react-16';
6 |
7 | const ColorPicker = require('../../../../../app/components/Settings/scheduler/pickers/color-picker.jsx');
8 |
9 | jest.mock('react-click-outside', () => i => i);
10 | describe('Color Picker Tests', () => {
11 | beforeAll(() => {
12 | configure({adapter: new Adapter()});
13 | // workaround `Error: Not implemented: HTMLCanvasElement.prototype.getContext`
14 | HTMLCanvasElement.prototype.getContext = function() {
15 | return null;
16 | };
17 | });
18 |
19 | it('should only call onClickAway if open', () => {
20 | const onClickAway = jest.fn();
21 | const component = mount( );
22 |
23 | component.instance().handleClickOutside();
24 | expect(component.find(ChromePicker).length).toBe(0);
25 | expect(onClickAway).not.toHaveBeenCalled();
26 |
27 | component.find('.color-box').simulate('click');
28 | component.update();
29 |
30 | expect(component.find(ChromePicker).length).toBe(1);
31 | component.instance().handleClickOutside();
32 | expect(onClickAway).toHaveBeenCalled();
33 | });
34 | });
35 |
--------------------------------------------------------------------------------
/test/app/components/Settings/scheduler/login-modal.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {mount, configure} from 'enzyme';
3 |
4 | import Adapter from 'enzyme-adapter-react-16';
5 |
6 | const LoginModal = require('../../../../../app/components/Settings/scheduler/modals/login-modal/login-modal.jsx');
7 |
8 | describe('Login Modal Tests', () => {
9 | beforeAll(() => {
10 | configure({adapter: new Adapter()});
11 | });
12 |
13 | it('should only set text if preview is truthy', () => {
14 | const component = mount( {}} onClickAway={() => {}} />);
15 |
16 | expect(component.find('CopyToClipboard').props().text).toBe('');
17 | });
18 |
19 | it('should set state on copy', async () => {
20 | jest.useFakeTimers();
21 | const component = mount(
22 | {}} onClickAway={() => {}} />
23 | );
24 |
25 | expect(
26 | component
27 | .find('button')
28 | .at(2)
29 | .text()
30 | ).toBe('Copy Query');
31 |
32 | component.instance().onCopy();
33 | expect(component.state('copyCoolingDown')).toBe(true);
34 | component.instance().onCopy();
35 | expect(
36 | component
37 | .find('button')
38 | .at(2)
39 | .text()
40 | ).toBe('Copied!');
41 |
42 | jest.runAllTimers();
43 |
44 | expect(component.state('copyCoolingDown')).toBe(false);
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/test/app/utils.js:
--------------------------------------------------------------------------------
1 | export const wait = (time = 0) => new Promise(resolve => setTimeout(resolve, time));
2 |
--------------------------------------------------------------------------------
/test/athena/SETUP_ATHENA.md:
--------------------------------------------------------------------------------
1 | # Additional Resources
2 | The following is a link to AWS Athena documentation on setting up an athena table and database
3 |
4 | [AWS Athean](https://docs.aws.amazon.com/athena/latest/ug/getting-started.html)
5 |
6 | In addition, there is a live tutorial built into the AWS Athena Console that provides
7 | step by step instructions with pictures on how to create a database and table (including
8 | sample data)
9 |
10 | # Setup
11 | The following is the high level instructions for setting up an Athena Database and Table.
12 |
13 | ## Requirements:
14 | 1. A valid AWS Account and access to the AWS Console
15 | 2. Experience working within the AWS Console
16 | 3. Experience creating S3 Buckets and pushing content to it
17 | 4. Experience with AWS IAMs for creating users and roles
18 |
19 |
20 | ## Instructions:
21 | 1. If you don't already have an S3 bucket create an S3 bucket and copy the athena-sample.csv to it
22 | 2. In the AWS Console Select Athena Service
23 | 3. If this is your first time create a database with Athena click on Getting Started
24 | 4. Click on Create Table
25 | 5. It will give you the option for Automatically (with Glue) or Manually. Choose Manually
26 | 6. For Database choose Create a new database
27 | 7. Enter in Database Name, Tablet Name, and the S3 Location where you uploaded the CSV file and then Next
28 | 8. Choose CSV for the option and Click on Next
29 | 9. Add the following 4 columns with the following information and then click on Next:
30 | i. location - string
31 | ii. alcohol - double,
32 | 10. Skip adding Partitions and click on Create Table
33 | 11. It will generate Create External Skip (see sample below). Click on Run Query (this will create table)
34 |
35 | ```sql
36 | CREATE EXTERNAL TABLE IF NOT EXISTS alcohol_consumption_by_country_2010 (
37 | `location` string,
38 | `alcohol` double,
39 | )
40 | ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe'
41 | WITH SERDEPROPERTIES (
42 | 'serialization.format' = ',',
43 | 'field.delim' = ','
44 | ) LOCATION 's3://plot.ly-athena/test-csv/'
45 | TBLPROPERTIES ('has_encrypted_data'='false');
46 | ```
47 | 12. Run sample SQL to see the results ( select * from alcohol_consumption_by_country_2010 ) and it should return sample data
--------------------------------------------------------------------------------
/test/backend/Connections.spec.js:
--------------------------------------------------------------------------------
1 | import {assert} from 'chai';
2 | import {assoc} from 'ramda';
3 | import fs from 'fs';
4 |
5 | import * as Connections from '../../backend/persistent/Connections.js';
6 | import {getSetting} from '../../backend/settings.js';
7 |
8 | describe('Connections', function () {
9 |
10 | beforeEach(function() {
11 | const connectionsPath = getSetting('CONNECTIONS_PATH');
12 | try {
13 | fs.unlinkSync(connectionsPath);
14 | } catch (e) {
15 | // empty intentionally
16 | }
17 | });
18 |
19 | it('saveConnection saves a connection', function() {
20 | assert.deepEqual(
21 | Connections.getConnections(),
22 | [],
23 | 'connections are empty'
24 | );
25 | const connectionObject = {
26 | dialect: 'postgres'
27 | };
28 | const connectionId = Connections.saveConnection(connectionObject);
29 | assert.deepEqual(
30 | Connections.getConnections(),
31 | [assoc('id', connectionId, connectionObject)],
32 | 'connection was saved'
33 | );
34 | });
35 | });
36 |
--------------------------------------------------------------------------------
/test/backend/Queries.spec.js:
--------------------------------------------------------------------------------
1 | import {assert} from 'chai';
2 |
3 | import { getQuery, saveQuery, updateQuery } from '../../backend/persistent/Queries';
4 | import {
5 | clearSettings
6 | } from './utils.js';
7 |
8 | const QUERY = {
9 | fid: 'fid'
10 | };
11 |
12 | // Suppressing ESLint cause Mocha ensures `this` is bound in test functions
13 | /* eslint-disable no-invalid-this*/
14 | describe('Queries', function() {
15 | beforeEach(function () {
16 | clearSettings('QUERIES_PATH');
17 | });
18 |
19 | it('updating subset of query data does not remove existing data', function () {
20 | const QUERY_WITH_SOME_DATA = {
21 | ...QUERY,
22 | initialData: '_'
23 | };
24 |
25 | saveQuery(QUERY_WITH_SOME_DATA);
26 |
27 | const newQueryData = { newData: '__' };
28 | updateQuery(QUERY_WITH_SOME_DATA.fid, newQueryData);
29 |
30 | const updatedQuery = getQuery(QUERY_WITH_SOME_DATA.fid);
31 | assert.equal(updatedQuery.initialData, '_');
32 | assert.equal(updatedQuery.newData, '__');
33 | });
34 |
35 | it('does not store undefined keys', function () {
36 | const QUERY_WITH_UNDEFINED_KEYS = {
37 | ...QUERY,
38 | /* eslint-disable no-undefined */
39 | undefinedKey: undefined,
40 | secondUndefinedKey: undefined
41 | };
42 |
43 | const initialQueryKeys = Object.keys(QUERY_WITH_UNDEFINED_KEYS);
44 | assert.equal(initialQueryKeys.includes('undefinedKey'), true);
45 | assert.equal(initialQueryKeys.includes('secondUndefinedKey'), true);
46 |
47 | saveQuery(QUERY_WITH_UNDEFINED_KEYS);
48 |
49 | const retrievedQueryKeys = Object.keys(getQuery(QUERY_WITH_UNDEFINED_KEYS.fid));
50 | assert.equal(retrievedQueryKeys.includes('undefinedKey'), false);
51 | assert.equal(retrievedQueryKeys.includes('secondUndefinedKey'), false);
52 | });
53 | });
--------------------------------------------------------------------------------
/test/backend/Tags.spec.js:
--------------------------------------------------------------------------------
1 | import {assert} from 'chai';
2 |
3 | import {getTags, getTag, saveTag, deleteTag, updateTag} from '../../backend/persistent/Tags';
4 | import {clearSettings} from './utils.js';
5 |
6 | const TAG = {name: 'name', color: 'color'};
7 | const TAG_2 = {name: 'name2', color: 'color2'};
8 |
9 | // Suppressing ESLint cause Mocha ensures `this` is bound in test functions
10 | /* eslint-disable no-invalid-this */
11 | describe('Tags', function() {
12 | beforeEach(function() {
13 | clearSettings('TAGS_PATH');
14 | });
15 |
16 | it('saves and retrieves individual tags', function() {
17 | const savedTag = saveTag(TAG);
18 | assert.deepEqual({...TAG, id: savedTag.id}, savedTag);
19 |
20 | const retrievedTag = getTag(savedTag.id);
21 | assert.deepEqual(savedTag, retrievedTag);
22 |
23 | const savedTag2 = saveTag(TAG_2);
24 | assert.deepEqual({...TAG_2, id: savedTag2.id}, savedTag2);
25 |
26 | const retrievedTag2 = getTag(savedTag2.id);
27 | assert.deepEqual(savedTag2, retrievedTag2);
28 | });
29 |
30 | it('updates tags', function() {
31 | const savedTag = saveTag(TAG);
32 | assert.deepEqual({...TAG, id: savedTag.id}, savedTag);
33 |
34 | const UPDATED_COLOR = 'updatedColor';
35 | updateTag(savedTag.id, {color: UPDATED_COLOR});
36 |
37 | const updatedTag = getTag(savedTag.id);
38 | assert.deepEqual(updatedTag, {...TAG, id: savedTag.id, color: UPDATED_COLOR});
39 | });
40 |
41 | it('gets all tags', function() {
42 | const savedTag = saveTag(TAG);
43 | const savedTag2 = saveTag(TAG_2);
44 | const savedTags = [savedTag, savedTag2];
45 |
46 | const retrievedTags = getTags();
47 | assert.deepEqual(savedTags, retrievedTags);
48 | });
49 |
50 | it('deletes tags', function() {
51 | const savedTag = saveTag(TAG);
52 | const savedTag2 = saveTag(TAG_2);
53 |
54 | assert.equal(getTags().length, 2);
55 |
56 | deleteTag(savedTag.id);
57 | const remainingTags = getTags();
58 | assert.equal(remainingTags.length, 1);
59 | assert.deepEqual(remainingTags[0], savedTag2);
60 |
61 | deleteTag(savedTag2.id);
62 | assert.equal(getTags().length, 0);
63 | });
64 | });
65 |
--------------------------------------------------------------------------------
/test/backend/datastores.clickhouse.spec.js:
--------------------------------------------------------------------------------
1 | import { assert } from 'chai';
2 |
3 | import { DIALECTS } from '../../app/constants/constants.js';
4 | import {
5 | connect,
6 | query,
7 | schemas,
8 | tables
9 | } from '../../backend/persistent/datastores/Datastores.js';
10 |
11 | const connection = {
12 | dialect: DIALECTS.CLICKHOUSE,
13 | host: 'localhost',
14 | port: 8123,
15 | username: 'default',
16 | password: '',
17 | database: 'plotly'
18 | };
19 |
20 | describe('ClickHouse', function() {
21 | it('connect succeeds', function() {
22 | return connect(connection);
23 | });
24 |
25 | it('tables returns list of tables', function() {
26 | return tables(connection).then(result => assert.include(result, 'consumption'));
27 | });
28 |
29 | it('schemas returns schemas for all tables', function() {
30 | return schemas(connection).then(({ rows, columnnames }) => {
31 | assert.deepInclude(rows, ['consumption', 'alcohol', 'Float32']);
32 | assert.deepInclude(rows, ['consumption', 'location', 'String']);
33 | assert.deepEqual(columnnames, ['tablename', 'column_name', 'data_type']);
34 | });
35 | });
36 |
37 | it('query returns rows and column names', function() {
38 | return query(
39 | 'SELECT * FROM consumption LIMIT 5',
40 | connection
41 | ).then(({ rows, columnnames }) => {
42 | assert.deepEqual(rows, [
43 | ['Belarus', 17.5],
44 | ['Moldova', 16.8],
45 | ['Lithuania', 15.4],
46 | ['Russia', 15.1],
47 | ['Romania', 14.4]
48 | ]);
49 | assert.deepEqual(columnnames, ['location', 'alcohol']);
50 | });
51 | });
52 |
53 | it('query rejects with an error when the query is invalid', function() {
54 | return query('invalid query', connection).catch(err => assert.exists(err));
55 | });
56 | });
--------------------------------------------------------------------------------
/test/backend/datastores.ibmdb.spec.js:
--------------------------------------------------------------------------------
1 | import {assert} from 'chai';
2 |
3 | import {DIALECTS} from '../../app/constants/constants.js';
4 |
5 | import {
6 | query, connect, tables
7 | } from '../../backend/persistent/datastores/Datastores.js';
8 |
9 | const connection = {
10 | dialect: DIALECTS.IBM_DB2,
11 | username: 'db2user1',
12 | password: 'w8wfy99DvEmgkBsE',
13 | host: 'db2.test.plotly.host',
14 | port: 50000,
15 | database: 'plotly'
16 | };
17 |
18 | // Circle CI uses test databases running locally on machine:
19 | if (process.env.CIRCLECI) {
20 | connection.host = 'localhost';
21 | }
22 |
23 | xdescribe('IBM DB2:', function () {
24 | it('connect succeeds', function(done) {
25 | connect(connection).then(database => {
26 | assert(database.connected);
27 | }).then(done);
28 | });
29 |
30 | it('query returns rows and column names', function(done) {
31 | query(
32 | 'SELECT * FROM DB2INST1.ALCOHOL_CONSUMPTION_BY_COUNTRY_2010 FETCH FIRST 5 ROWS ONLY',
33 | connection
34 | ).then(results => {
35 | assert.deepEqual(results.rows, [
36 | ['Belarus', 17.5],
37 | ['Moldova', 16.8],
38 | ['Lithuania', 15.4],
39 | ['Russia', 15.1],
40 | ['Romania', 14.4]
41 | ]);
42 | assert.deepEqual(results.columnnames, ['LOCATION', 'ALCOHOL']);
43 | }).then(done);
44 | });
45 |
46 | it('tables returns list of tables', function(done) {
47 | tables(connection).then(result => {
48 | assert.deepEqual(result, ['DB2INST1.ALCOHOL_CONSUMPTION_BY_COUNTRY_2010']);
49 | }).then(done);
50 | });
51 | });
52 |
--------------------------------------------------------------------------------
/test/backend/datastores.impala.spec.js:
--------------------------------------------------------------------------------
1 | import {assert} from 'chai';
2 |
3 | import {apacheImpalaConnection as connection} from './utils.js';
4 | import {
5 | query, connect, tables
6 | } from '../../backend/persistent/datastores/Datastores.js';
7 |
8 | // Suppressing ESLint cause Mocha ensures `this` is bound in test functions
9 | /* eslint-disable no-invalid-this */
10 | describe.skip('Apache Impala:', function () {
11 |
12 | it('connect succeeds', function() {
13 | this.timeout(180 * 1000);
14 | return connect(connection);
15 | });
16 |
17 | it('tables returns list of tables', function() {
18 | return tables(connection).then(result => {
19 | const tableName = (connection.database) ?
20 | `${connection.database}.ALCOHOL_CONSUMPTION_BY_COUNTRY_2010`.toUpperCase() :
21 | 'ALCOHOL_CONSUMPTION_BY_COUNTRY_2010';
22 |
23 | assert.deepEqual(result, [tableName]);
24 | });
25 | });
26 |
27 | it('query returns rows and column names', function() {
28 | const tableName = (connection.database) ?
29 | `${connection.database}.ALCOHOL_CONSUMPTION_BY_COUNTRY_2010`.toUpperCase() :
30 | 'ALCOHOL_CONSUMPTION_BY_COUNTRY_2010';
31 |
32 | return query(`SELECT * FROM ${tableName}\nLIMIT 5`, connection).then(results => {
33 | assert.deepEqual(results.rows, [
34 | ['Belarus', '17.5'],
35 | ['Moldova', '16.8'],
36 | ['Lithuania', '15.4'],
37 | ['Russia', '15.1'],
38 | ['Romania', '14.4']
39 | ]);
40 | assert.deepEqual(results.columnnames, ['loc', 'alcohol']);
41 | });
42 | });
43 |
44 | it('connect for invalid credentials fails', function() {
45 | connection.host = 'http://lah-lah.lemons.com';
46 |
47 | return connect(connection).catch(err => {
48 | // reset hostname
49 | connection.host = '35.184.155.127';
50 |
51 | assert.equal(err, ('Error: Error: getaddrinfo ENOTFOUND ' +
52 | 'http://lah-lah.lemons.com ' +
53 | 'http://lah-lah.lemons.com:21000'));
54 | });
55 | });
56 | });
57 | /* eslint-enable no-invalid-this */
58 |
--------------------------------------------------------------------------------
/test/backend/datastores.oracle.spec.js:
--------------------------------------------------------------------------------
1 | import {assert} from 'chai';
2 |
3 | import {DIALECTS} from '../../app/constants/constants.js';
4 |
5 | import {
6 | connect,
7 | disconnect,
8 | query,
9 | schemas,
10 | tables
11 | } from '../../backend/persistent/datastores/Datastores.js';
12 |
13 | const connection = {
14 | dialect: DIALECTS.ORACLE,
15 | username: 'XDB',
16 | password: 'xdb',
17 | connectionString: 'localhost/XE'
18 | };
19 |
20 | // Skip tests if there is no working installation of oracledb
21 | let oracledb;
22 | try {
23 | oracledb = require('oracledb');
24 | } catch (err) {
25 | if (!process.env.CIRCLECI) {
26 | console.log('Skipping `datastores.oracle.spec.js`:', err); // eslint-disable-line
27 | }
28 | }
29 |
30 | ((oracledb) ? describe : xdescribe)('Oracle:', function () {
31 | it('connect succeeds', function() {
32 | return connect(connection);
33 | });
34 |
35 | it('tables returns list of tables', function() {
36 | return tables(connection).then(result => {
37 | assert.include(result, 'CONSUMPTION2010', result);
38 | });
39 | });
40 |
41 | it('schemas returns schemas for all tables', function() {
42 | return schemas(connection).then(results => {
43 | assert.deepInclude(results.rows, ['CONSUMPTION2010', 'ALCOHOL', 'NUMBER']);
44 | assert.deepInclude(results.rows, ['CONSUMPTION2010', 'LOCATION', 'VARCHAR2']);
45 | assert.deepEqual(results.columnnames, ['TABLE_NAME', 'COLUMN_NAME', 'DATA_TYPE']);
46 | });
47 | });
48 |
49 | it('query returns rows and column names', function() {
50 | return query(
51 | 'SELECT * FROM CONSUMPTION2010 WHERE ROWNUM <= 5',
52 | connection
53 | ).then(results => {
54 | assert.deepEqual(results.rows, [
55 | ['Belarus', 17.5],
56 | ['Moldova', 16.8],
57 | ['Lithuania', 15.4],
58 | ['Russia', 15.1],
59 | ['Romania', 14.4]
60 | ]);
61 | assert.deepEqual(results.columnnames, ['LOCATION', 'ALCOHOL']);
62 | });
63 | });
64 |
65 | it('disconnect succeeds', function() {
66 | return disconnect(connection);
67 | });
68 | });
69 |
--------------------------------------------------------------------------------
/test/backend/parse.spec.js:
--------------------------------------------------------------------------------
1 | import {parseCSV} from '../../backend/parse.js';
2 | import {assert} from 'chai';
3 |
4 | const TEST_CASES = [
5 | {
6 | input: 'Aardvark,Banana Camel\n3.14,4',
7 | output: [['Aardvark', 'Banana Camel'], ['3.14', '4']],
8 | name: 'simple'
9 | },
10 | {
11 | input: 'Aardvark,"Banana, Camel"\n3.14,4',
12 | output: [['Aardvark', 'Banana, Camel'], ['3.14', '4']],
13 | name: 'Quote protected commas'
14 | },
15 | {
16 | input: 'Aardvark, " Banana, Camel"\n3.14, 2015-01-01 12:10:14',
17 | output: [['Aardvark', ' Banana, Camel'], ['3.14', '2015-01-01 12:10:14']],
18 | name: 'Extra spaces and dates'
19 | },
20 | {
21 | input: 'Single',
22 | output: [['Single']],
23 | name: 'single entry'
24 | },
25 | {
26 | input: '',
27 | output: [[], []],
28 | name: 'Empty'
29 | }
30 | ];
31 |
32 | describe('parseCSV', function() {
33 | TEST_CASES.forEach(function(testCase) {
34 | const {name, input, output} = testCase;
35 | it(`Parses a simple CSV string for test case ${name}`, function(done) {
36 | parseCSV(input).then(function(rowsAndColumns) {
37 | assert.deepEqual(
38 | output[0], rowsAndColumns.columnnames
39 | );
40 | assert.deepEqual(
41 | output.slice(1), rowsAndColumns.rows
42 | );
43 | done();
44 | }).catch(done);
45 | });
46 | });
47 | });
48 |
--------------------------------------------------------------------------------
/test/backend/plotly_datasets.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plotly/falcon/843628318423364073d8488b0fbc9cec0b1ecc92/test/backend/plotly_datasets.db
--------------------------------------------------------------------------------
/test/docker/README.md:
--------------------------------------------------------------------------------
1 | Test databases
2 | --------------
3 |
4 | This directory contains DockerFiles and deployment configurations for creating the test instances of following databases:
5 |
6 | * Apache Impala
7 | * Apache Spark
8 | * IBM DB2
9 |
10 |
11 |
12 | Commands
13 | ========
14 |
15 | To Create GKE Cluster:
16 |
17 | ```sh
18 | gcloud container clusters create falcon-test-dbs --zone us-central1-b --machine-type n1-standard-2 --num-nodes 3 --enable-autoupgrade
19 | ```
20 |
21 | To create deployments for test databases:
22 |
23 | ```sh
24 | kubectl apply -f deployment/
25 | ```
26 |
--------------------------------------------------------------------------------
/test/docker/clickhouse/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM yandex/clickhouse-server:18.14
2 |
3 | ADD https://raw.githubusercontent.com/plotly/datasets/master/2010_alcohol_consumption_by_country.csv /2010_alcohol_consumption_by_country.csv
4 |
5 | RUN sed -i -e "1d" /2010_alcohol_consumption_by_country.csv
6 |
7 | RUN chmod 777 /2010_alcohol_consumption_by_country.csv
8 |
9 | RUN apt-get -y update && apt-get -y install curl clickhouse-client
10 |
11 | COPY setup.sh /
12 |
13 | RUN chmod +x /setup.sh
14 |
15 | EXPOSE 9000 8123
16 |
17 | CMD /setup.sh
--------------------------------------------------------------------------------
/test/docker/clickhouse/README.md:
--------------------------------------------------------------------------------
1 | The Dockerfile in this folder builds a Docker image that starts a ClickHouse server daemon and sets up a test database.
2 |
3 | # License
4 |
5 | This Dockerfile uses [yandex/clickhouse-server](https://hub.docker.com/r/yandex/clickhouse-server/) as a base image.
6 |
7 | Please note that Yandex ClickHouse is licensed under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) and you may not use this except in compliance with the License.
8 |
9 | # Usage
10 |
11 | ## Build
12 |
13 | Run the command below in the folder where the Dockerfile is located:
14 |
15 | ```sh
16 | docker build . -t falcon-clickhouse
17 | ```
18 |
19 | ## Run
20 |
21 | Run the command below inside a terminal, to start an instance listening on ports 8123 and 9000:
22 |
23 | ```sh
24 | docker run -ti -p 8123:8123 -p 9000:9000 falcon-clickhouse
25 | ```
26 |
27 | To stop the container, just press `CTRL-C`.
--------------------------------------------------------------------------------
/test/docker/clickhouse/setup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | clickhouse-server --config-file=/etc/clickhouse-server/config.xml &
5 |
6 | # Wait for ClickHouse server to be ready
7 | until $(curl --output /dev/null --silent --head --fail http://localhost:8123); do
8 | echo -e -n '.'
9 | sleep 2.5
10 | done
11 |
12 | clickhouse-client --query="CREATE DATABASE plotly";
13 |
14 | clickhouse-client --database=plotly --query="CREATE TABLE consumption (location String, alcohol Float32) ENGINE = Memory";
15 |
16 | cat /2010_alcohol_consumption_by_country.csv | clickhouse-client --database=plotly --query="INSERT INTO consumption FORMAT CSV";
17 |
18 | # Keep ClickHouse server alive
19 | while true
20 | do
21 | sleep 30
22 | done
23 |
--------------------------------------------------------------------------------
/test/docker/deployment/db2-deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: extensions/v1beta1
2 | kind: Deployment
3 | metadata:
4 | labels:
5 | name: db2-node
6 | name: db2-node
7 | spec:
8 | replicas: 3
9 | template:
10 | metadata:
11 | labels:
12 | name: db2-node
13 | spec:
14 | containers:
15 | - image: gcr.io/plotly-testing/falcon/test-db2
16 | name: db2-node
17 | ports:
18 | - containerPort: 50000
19 |
--------------------------------------------------------------------------------
/test/docker/deployment/db2-service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | labels:
5 | name: db2-service
6 | name: db2-service
7 | spec:
8 | ports:
9 | - protocol: TCP
10 | port: 50000
11 | targetPort: 50000
12 | selector:
13 | name: db2-node
14 | type: LoadBalancer
15 |
--------------------------------------------------------------------------------
/test/docker/deployment/impala-deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: extensions/v1beta1
2 | kind: Deployment
3 | metadata:
4 | labels:
5 | name: impala-node
6 | name: impala-node
7 | spec:
8 | replicas: 3
9 | template:
10 | metadata:
11 | labels:
12 | name: impala-node
13 | spec:
14 | containers:
15 | - image: gcr.io/plotly-testing/falcon/test-impala
16 | name: impala-node
17 | ports:
18 | - containerPort: 21000
19 |
--------------------------------------------------------------------------------
/test/docker/deployment/impala-service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | labels:
5 | name: impala-service
6 | name: impala-service
7 | spec:
8 | ports:
9 | - protocol: TCP
10 | port: 21000
11 | targetPort: 21000
12 | selector:
13 | name: impala-node
14 | type: LoadBalancer
15 |
--------------------------------------------------------------------------------
/test/docker/deployment/spark-deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: extensions/v1beta1
2 | kind: Deployment
3 | metadata:
4 | labels:
5 | name: spark-node
6 | name: spark-node
7 | spec:
8 | replicas: 3
9 | template:
10 | metadata:
11 | labels:
12 | name: spark-node
13 | spec:
14 | containers:
15 | - image: gcr.io/plotly-testing/falcon/test-spark
16 | name: spark-node
17 | ports:
18 | - containerPort: 8998
19 |
--------------------------------------------------------------------------------
/test/docker/deployment/spark-service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | labels:
5 | name: spark-service
6 | name: spark-service
7 | spec:
8 | ports:
9 | - protocol: TCP
10 | port: 8998
11 | targetPort: 8998
12 | selector:
13 | name: spark-node
14 | type: LoadBalancer
15 |
--------------------------------------------------------------------------------
/test/docker/elasticsearch/Dockerfile-v5:
--------------------------------------------------------------------------------
1 | FROM docker.elastic.co/elasticsearch/elasticsearch:5.6.7
2 |
3 | USER elasticsearch
4 |
5 | COPY setup-v5.bash .
6 | COPY test-types.elastic-2.4-types.ndjson .
7 | COPY sample-data.test-type.ndjson .
8 |
9 | RUN elasticsearch -d -Ediscovery.type=single-node -Expack.security.enabled=false && bash setup-v5.bash
10 |
--------------------------------------------------------------------------------
/test/docker/elasticsearch/test-types.elastic-2.4-types.ndjson:
--------------------------------------------------------------------------------
1 | {"index":{"_index":"test-types","_type":"elastic-2.4-types","_id":"1"}}
2 | {"ip":"205.7.19.54","geo_point-2":"-45.0649063041,-11.9183865969","double":0.6310475331632162,"token":"Hector Jai Brenna Mary Gabrielle","boolean":true,"string-1":"connection harmonies camp loss customs","string-2":"Cyprus","geo_point-3":[-11.918386596878577,-45.06490630412356],"date":"2016-12-04T16:05:13.816943-05:00","integer":0,"geo_point-1":{"lat":-45.06490630412356,"lon":-11.918386596878577}}
3 | {"index":{"_index":"test-types","_type":"elastic-2.4-types","_id":"2"}}
4 | {"ip":"208.49.20.91","geo_point-2":"73.4598645169,94.969768064","double":0.04016600628707845,"token":"Winston Aubrey Perla","boolean":true,"string-1":"meters recruiter chases villages platter","string-2":"South Africa","geo_point-3":[94.96976806404194,73.45986451687563],"date":"2016-12-04T16:04:43.816943-05:00","integer":1,"geo_point-1":{"lat":73.45986451687563,"lon":94.96976806404194}}
5 | {"index":{"_index":"test-types","_type":"elastic-2.4-types","_id":"3"}}
6 | {"ip":"134.119.173.12","geo_point-2":"57.491330009,-28.0041090462","double":0.4387754352694577,"token":"Jorge Arthur Scott Kaleigh Chad Roanne","boolean":true,"string-1":"kites clump circles reduction parcel","string-2":"Angola","geo_point-3":[-28.004109046235016,57.4913300090476],"date":"2016-12-04T16:04:13.816943-05:00","integer":2,"geo_point-1":{"lat":57.4913300090476,"lon":-28.004109046235016}}
7 |
--------------------------------------------------------------------------------
/test/docker/ibmdb2/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ibmcom/db2express-c:latest
2 |
3 | ARG DB2USER1_PASSWORD
4 | ARG DB2INST1_PASSWORD
5 | ARG LICENSE
6 |
7 | COPY "build.sh" "/build.sh"
8 |
9 | RUN curl https://raw.githubusercontent.com/plotly/datasets/master/2010_alcohol_consumption_by_country.csv -o /2010_alcohol_consumption_by_country.csv
10 |
11 | # Install DB2 Express-C
12 | RUN /entrypoint.sh "true"
13 |
14 | # Setup sample database
15 | RUN /build.sh
16 |
17 | # Start database instance
18 | ENTRYPOINT su - db2inst1 -c db2start && (while true; do sleep 1000; done)
19 |
20 | EXPOSE 50000
21 |
--------------------------------------------------------------------------------
/test/docker/ibmdb2/README.md:
--------------------------------------------------------------------------------
1 | The Dockerfile in this folder builds a Docker image that starts an instance of
2 | IBM DB2 Express-C with an admin user (db2inst1), a SELECT-only user (db2user1),
3 | and a database (sample).
4 |
5 |
6 | # License
7 |
8 | This Dockerfile uses
9 | [ibmcom/db2express-c](https://hub.docker.com/r/ibmcom/db2express-c/) as a base
10 | image.
11 |
12 | Please, note that IBM DB2 Express-C is [licensed under the IBM International
13 | License Agreement for Non-Warranted
14 | Programs](http://www-03.ibm.com/software/sla/sladb.nsf/displaylis/5DF1EE126832D3F185257DAB0064BEFA?OpenDocument),
15 | which does not permit further distribution. And therefore, it does not permit
16 | the distribution of images built using this Dockerfile.
17 |
18 |
19 | # Usage
20 |
21 |
22 | ## Build
23 |
24 | Run the command below in the folder where the Dockerfile is located (This
25 | command may take several minutes to complete):
26 |
27 | ```sh
28 | docker build . -t pdc-db2 --build-arg LICENSE=accept --build-arg DB2INST1_PASSWORD= --build-arg DB2USER1_PASSWORD= --no-cache
29 |
30 | ```
31 |
32 | Note that to run the command above is necessary to:
33 |
34 | - accept [the IBM International License Agreement for Non-Warranted
35 | Programs](http://www-03.ibm.com/software/sla/sladb.nsf/displaylis/5DF1EE126832D3F185257DAB0064BEFA?OpenDocument)
36 | by setting `LICENSE` to `accept`.
37 |
38 | - set the admin's password by setting `DB2INST1_PASSWORD`
39 |
40 | - set the SELECT-only user's password by setting `DB2USER_PASSWORD`
41 |
42 |
43 |
44 | ## Run
45 |
46 | Run the command below inside a terminal, to start an instance listening on port
47 | 50000:
48 |
49 | ```sh
50 | docker run -ti -p 50000:50000 pdc-db2
51 | ```
52 |
53 | To stop the container, just press `CTRL-C`.
54 |
--------------------------------------------------------------------------------
/test/docker/ibmdb2/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | DB_ADMIN=db2inst1
5 | DB_USER=db2user1
6 |
7 | case $(whoami) in
8 | root)
9 | # Setup user ${DB_USER}
10 | adduser ${DB_USER}
11 | (echo ${DB2USER1_PASSWORD}; echo ${DB2USER1_PASSWORD}) | passwd ${DB_USER} > /dev/null 2>&1
12 |
13 | # Grant SELECT privilege on tables in schema ${DB_ADMIN} to user ${DB_USER}
14 | su - $DB_ADMIN -c $0
15 |
16 | ;;
17 |
18 | ${DB_ADMIN})
19 | # Start database instance
20 | db2start
21 |
22 | db2 create database plotly
23 |
24 | # Grant CONNECT privilege on database user ${DB_USER}
25 | db2 connect to plotly
26 |
27 | db2 "CREATE TABLE alcohol_consumption_by_country_2010 (Location nvarchar(64), Alcohol double)"
28 | db2 import from /2010_alcohol_consumption_by_country.csv of del skipcount 1 insert into alcohol_consumption_by_country_2010 || true
29 |
30 | echo db2 grant connect on database to user ${DB_USER}
31 | db2 grant connect on database to user ${DB_USER}
32 |
33 | db2 connect to plotly
34 | echo db2 grant select on ${DB_ADMIN}.alcohol_consumption_by_country_2010 to user ${DB_USER}
35 | db2 grant select on ${DB_ADMIN}.alcohol_consumption_by_country_2010 to user ${DB_USER}
36 |
37 | # Stop database instance
38 | db2 force applications all
39 | db2 terminate
40 | db2stop
41 |
42 | ;;
43 |
44 | *)
45 | echo "error: '$0' must be invoked by root"
46 |
47 | esac
48 |
--------------------------------------------------------------------------------
/test/docker/impala/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM codingtony/impala
2 |
3 | # Copy test-db scripts:
4 | COPY bin/* /bin/
5 | RUN chmod +x /bin/setup_database.sh;
6 | RUN chmod +x /bin/setup_test_db.sql;
7 |
8 | EXPOSE 9000 50010 50020 50070 50075 21000 21050 25000 25010 25020
9 |
10 | CMD /start.sh && /bin/setup_database.sh;
11 |
--------------------------------------------------------------------------------
/test/docker/impala/README.md:
--------------------------------------------------------------------------------
1 | The Dockerfile in this folder builds a Docker image that starts the hadoop components, impala daemons
2 | and sets up a test database.
3 |
4 | This Dockerfile uses
5 | [codingtony/docker-impala](https://hub.docker.com/r/codingtony/impala/) as a base
6 | image.
7 |
8 | # Usage
9 |
10 |
11 | ## Build
12 |
13 | Run the command below in the folder where the Dockerfile is located (This
14 | command may take several minutes to complete):
15 |
16 | ```sh
17 | docker build . -t falcon-impala
18 |
19 | ```
20 |
21 |
22 | ## Run
23 |
24 | Run the command below inside a terminal, to start an instance listening on ports
25 | 25000 and 21000:
26 |
27 | ```sh
28 | docker run -ti -p 25000:25000 -p 21000:21000 falcon-impala
29 | ```
30 |
31 | Visit http://localhost:25000 to check the status of running impala daemons.
32 | To stop the container, just press `CTRL-C`.
33 |
--------------------------------------------------------------------------------
/test/docker/impala/bin/setup_database.sh:
--------------------------------------------------------------------------------
1 | apt-get -y update;
2 | apt-get -y install unzip curl;
3 | cd / && curl https://codeload.github.com/plotly/datasets/zip/master >| plotly_datasets.zip
4 |
5 | # unzip datasets:
6 | unzip plotly_datasets.zip
7 | mv datasets-master /plotly_datasets
8 |
9 | # Strip header line from test csv:
10 | sed -i -e "1d" /plotly_datasets/2010_alcohol_consumption_by_country.csv
11 |
12 |
13 | # Upload to hdfs as 'impala' user
14 | su - impala -c "hdfs dfs -put /plotly_datasets/2010_alcohol_consumption_by_country.csv /user/impala/2010_alcohol_consumption_by_country.csv"
15 |
16 | impala-shell -f /bin/setup_test_db.sql
17 |
18 | # To keep the container running:
19 | while true
20 | do
21 | sleep 30
22 | echo "Impala-test db is up and running."
23 | done
24 |
--------------------------------------------------------------------------------
/test/docker/impala/bin/setup_test_db.sql:
--------------------------------------------------------------------------------
1 | CREATE DATABASE PLOTLY;
2 | USE PLOTLY;
3 | CREATE TABLE ALCOHOL_CONSUMPTION_BY_COUNTRY_2010 (LOC STRING, ALCOHOL DOUBLE) ROW FORMAT DELIMITED FIELDS TERMINATED BY ',';
4 | LOAD DATA INPATH '/user/impala/2010_alcohol_consumption_by_country.csv' OVERWRITE INTO TABLE ALCOHOL_CONSUMPTION_BY_COUNTRY_2010;
5 |
--------------------------------------------------------------------------------
/test/docker/oracle/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM wnameless/oracle-xe-11g:16.04
2 |
3 | EXPOSE 1521
4 |
5 | ADD https://raw.githubusercontent.com/plotly/datasets/master/2010_alcohol_consumption_by_country.csv /2010_alcohol_consumption_by_country.csv
6 | COPY setup.sql /
7 | COPY setup.ctl /
8 | COPY setup.sh /docker-entrypoint-initdb.d/
9 |
10 | ENV ORACLE_ENABLE_XDB true
11 |
--------------------------------------------------------------------------------
/test/docker/oracle/README.md:
--------------------------------------------------------------------------------
1 | The Dockerfile in this folder builds a Docker image that starts an instance of
2 | Oracle Database 11g Express Edition with the logins `SYSTEM/oracle` and
3 | `XDB/xdb`, and the sample database `consumption2010`.
4 |
5 |
6 | # License
7 |
8 | This Dockerfile uses
9 | [wnameless/oracle-xe-11g](https://hub.docker.com/r/wnameless/oracle-xe-11g/) as
10 | a base image.
11 |
12 | Please, note that Oracle Database Express Edition is [licensed under the Oracle
13 | Technology Network Developer License
14 | Terms](http://www.oracle.com/technetwork/licenses/database-11g-express-license-459621.html).
15 |
16 |
17 | # Usage
18 |
19 |
20 | ## Build
21 |
22 | Run the command below in the folder where the Dockerfile is located:
23 |
24 | ```sh
25 | docker build . -t falcon-test-oracle
26 |
27 | ```
28 |
29 |
30 | ## Run
31 |
32 | Run the command below inside a terminal, to start an instance listening on port
33 | 1521:
34 |
35 | ```sh
36 | docker run --rm -ti -p 1521:1521 falcon-test-oracle
37 | ```
38 |
39 | To stop the container, just press `CTRL-C`.
40 |
--------------------------------------------------------------------------------
/test/docker/oracle/setup.ctl:
--------------------------------------------------------------------------------
1 | OPTIONS(SKIP=1)
2 |
3 | LOAD DATA
4 | INFILE "/2010_alcohol_consumption_by_country.csv"
5 |
6 | REPLACE
7 | INTO TABLE consumption2010
8 |
9 | FIELDS TERMINATED BY ","
10 | OPTIONALLY ENCLOSED BY '"'
11 | (location, alcohol)
12 |
--------------------------------------------------------------------------------
/test/docker/oracle/setup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | sqlplus XDB/xdb @/setup.sql
5 | sqlldr userid=XDB/xdb control=/setup.ctl
6 |
7 | echo Ready
8 |
--------------------------------------------------------------------------------
/test/docker/oracle/setup.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE consumption2010 (
2 | location varchar2(50),
3 | alcohol number
4 | );
5 | QUIT
6 |
--------------------------------------------------------------------------------
/test/docker/spark/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM sequenceiq/hadoop-ubuntu:2.6.0
2 |
3 | # Get latest java:
4 | COPY bin/setup_java.sh /setup_java.sh
5 | RUN chmod +x /setup_java.sh;
6 | RUN /setup_java.sh;
7 | ENV JAVA_HOME=/usr/lib/jvm/java-8-oracle/jre
8 |
9 | # Get hive:
10 | ARG HIVE_VERSION=2.2.0
11 | RUN wget http://www-eu.apache.org/dist/hive/hive-$HIVE_VERSION/apache-hive-$HIVE_VERSION-bin.tar.gz && \
12 | tar -xzvf apache-hive-$HIVE_VERSION-bin.tar.gz && \
13 | mv apache-hive-$HIVE_VERSION-bin hive;
14 |
15 | ENV HIVE_HOME=/hive \
16 | PATH=/hive/bin:$PATH \
17 | HIVE_CONF_DIR=/hive/conf/
18 |
19 | COPY conf/* $HIVE_CONF_DIR
20 | COPY conf/my.cnf /etc/
21 |
22 | # Install mysql as hive metastore
23 | COPY setup_mysql.sh /setup_mysql.sh
24 | RUN chmod +x /setup_mysql.sh;
25 | RUN /setup_mysql.sh;
26 | # RUN /hive/bin/schematool -dbType mysql -initSchema -verbose;
27 |
28 | # Install spark
29 | RUN curl -s http://apache.stu.edu.tw/spark/spark-2.2.0/spark-2.2.0-bin-hadoop2.6.tgz >| spark-2.2.0.tgz
30 | RUN tar -xvf spark-2.2.0.tgz -C /usr/local/ && cd /usr/local && ln -s spark-2.2.0-bin-hadoop2.6 spark
31 |
32 | ENV SPARK_JAR=hdfs:///spark/spark-assembly-2.2.0-hadoop2.6.0.jar \
33 | SPARK_HOME=/usr/local/spark \
34 | SPARK_HIVE=true \
35 | PATH=$PATH:/usr/local/spark/bin \
36 | PYTHONPATH=/usr/local/spark/python/:$PYTHONPATH \
37 | PYTHONPATH=/usr/local/spark/python/lib/py4j-0.8.2.1-src.zip:$PYTHONPATH
38 |
39 | RUN cp -r $HIVE_HOME/lib/mysql.jar $SPARK_HOME/jars/
40 | COPY conf/* $SPARK_HOME/conf/
41 | COPY bin/* /bin/
42 |
43 | # Setup Livy
44 | RUN chmod +x /bin/setup_livy.sh;
45 | RUN /bin/setup_livy.sh;
46 | COPY conf/livy.conf /livy/conf/
47 |
48 | # Workaround for mysql
49 | RUN echo "skip-grant-tables" | tee -a /etc/my.cnf && service mysql restart; exit 0;
50 |
51 | EXPOSE 8998
52 |
53 | ENTRYPOINT /etc/bootstrap.sh && service mysql start && chmod +x /bin/setup_database.sh && sync && /bin/setup_database.sh && /livy/bin/livy-server;
54 |
--------------------------------------------------------------------------------
/test/docker/spark/README.md:
--------------------------------------------------------------------------------
1 |
2 | The Dockerfile in this folder builds a Docker image that starts the hadoop components, spark daemons, sets up a test database and runs apache-livy server.
3 |
4 | This Dockerfile uses
5 | [sequenceiq/hadoop-ubuntu](https://hub.docker.com/r/sequenceiq/hadoop-ubuntu/) as a base
6 | image.
7 |
8 | ## Build
9 |
10 | Run the command below in the folder where the Dockerfile is located (This
11 | command may take several minutes to complete):
12 |
13 | ```sh
14 | docker build . -t falcon-spark
15 |
16 | ```
17 |
18 |
19 | ## Run
20 |
21 | Run the command below inside a terminal, to start an instance listening on port 8998:
22 |
23 | ```sh
24 | docker run -ti -p 8998:8998 falcon-spark
25 | ```
26 |
27 | Visit http://localhost:8998 to check the status of running livy server and information about running sessions.
28 | To stop the container, just press `CTRL-C`.
29 |
--------------------------------------------------------------------------------
/test/docker/spark/bin/create_hive_tables.py:
--------------------------------------------------------------------------------
1 | import sys
2 | from pyspark import SparkContext, SparkConf, HiveContext, SQLContext
3 |
4 | if __name__ == '__main__':
5 | conf = SparkConf().setAppName("Plotly Exports")
6 | sc = SparkContext(conf=conf)
7 | hive_context = HiveContext(sc)
8 | print '=== Creating Database ==='
9 | hive_context.sql('CREATE DATABASE PLOTLY')
10 | hive_context.sql('USE PLOTLY')
11 |
12 | print '=== Creating Table ==='
13 | hive_context.sql("CREATE TABLE ALCOHOL_CONSUMPTION_BY_COUNTRY_2010 "
14 | "(LOCATION STRING, ALCOHOL DOUBLE) ROW FORMAT "
15 | "DELIMITED FIELDS TERMINATED BY ',' "
16 | "TBLPROPERTIES (\"skip.header.line.count\"=\"1\")")
17 | print "=== loading data into table ==="
18 | hive_context.sql("LOAD DATA LOCAL INPATH "
19 | "'/plotly_datasets/2010_alcohol_consumption_by_country.csv' "
20 | "OVERWRITE INTO TABLE ALCOHOL_CONSUMPTION_BY_COUNTRY_2010")
21 | sys.exit()
22 |
--------------------------------------------------------------------------------
/test/docker/spark/bin/livy_python_requirements.txt:
--------------------------------------------------------------------------------
1 | cloudpickle
2 | requests
3 | requests-kerberos
4 | flake8
5 | flaky
6 | pytest
7 |
--------------------------------------------------------------------------------
/test/docker/spark/bin/setup_database.sh:
--------------------------------------------------------------------------------
1 | # Fetch Plotly master datasets to $HOME.
2 | apt-get install unzip
3 | cd && curl https://codeload.github.com/plotly/datasets/zip/master >| plotly_datasets.zip
4 |
5 | # unzip datasets:
6 | unzip plotly_datasets.zip
7 | mv datasets-master /plotly_datasets
8 |
9 | # Strip header line from test csv:
10 | sed -i -e "1d" /plotly_datasets/2010_alcohol_consumption_by_country.csv
11 |
12 | # create table
13 | spark-submit /bin/create_hive_tables.py
14 |
--------------------------------------------------------------------------------
/test/docker/spark/bin/setup_java.sh:
--------------------------------------------------------------------------------
1 | apt-get update
2 | apt-get install --assume-yes software-properties-common python-software-properties git
3 | add-apt-repository ppa:webupd8team/java -y
4 | apt-get update
5 |
6 | echo debconf shared/accepted-oracle-license-v1-1 select true | sudo debconf-set-selections
7 | echo debconf shared/accepted-oracle-license-v1-1 seen true | sudo debconf-set-selections
8 | sudo apt-get install --assume-yes oracle-java8-installer
9 | export JAVA_HOME=/usr/lib/jvm/java-8-oracle
10 |
--------------------------------------------------------------------------------
/test/docker/spark/bin/setup_livy.sh:
--------------------------------------------------------------------------------
1 | # Install maven:
2 | wget http://mirror.olnevhost.net/pub/apache/maven/binaries/apache-maven-3.2.1-bin.tar.gz
3 | tar xvf apache-maven-3.2.1-bin.tar.gz
4 | export M2=/apache-maven-3.2.1/bin
5 | export PATH=$M2:$PATH
6 |
7 |
8 | apt-get install --assume-yes python-dev python-setuptools python-pip python-wheel libkrb5-dev
9 |
10 | # Upgrade pip:
11 | pip install -U pip wheel setuptools
12 | pip install -r /bin/livy_python_requirements.txt
13 |
14 | # Get livy:
15 | git clone https://github.com/cloudera/livy.git
16 | cd livy
17 | JAVA_HOME=/usr/lib/jvm/java-8-oracle/jre MAVEN_OPTS="-Xmx512m -XX:MaxPermSize=256m" /apache-maven-3.2.1/bin/mvn -DskipTests clean package
18 |
19 | # Setup livy logging directory
20 | mkdir -p /livy/logs;
21 |
--------------------------------------------------------------------------------
/test/docker/spark/conf/hive-env.sh:
--------------------------------------------------------------------------------
1 | # Thanks to https://community.hortonworks.com/questions/9079/remote-debug-hiveserver2.html
2 | # Enable remote debugging of hiveserver2.
3 | if [ "$SERVICE" = "hiveserver2" ]; then
4 | export HADOOP_OPTS="$HADOOP_OPTS -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8001"
5 | fi
6 |
7 |
--------------------------------------------------------------------------------
/test/docker/spark/conf/hive-log4j2.properties:
--------------------------------------------------------------------------------
1 | # Licensed to the Apache Software Foundation (ASF) under one
2 | # or more contributor license agreements. See the NOTICE file
3 | # distributed with this work for additional information
4 | # regarding copyright ownership. The ASF licenses this file
5 | # to you under the Apache License, Version 2.0 (the
6 | # "License"); you may not use this file except in compliance
7 | # with the License. You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | status = INFO
18 | name = HiveLog4j2
19 | packages = org.apache.hadoop.hive.ql.log
20 |
21 | # list of properties
22 | property.hive.log.level = DEBUG
23 | property.hive.root.logger = DRFA
24 | property.hive.log.dir = ${sys:java.io.tmpdir}/${sys:user.name}
25 | property.hive.log.file = hive.log
26 |
27 | # list of all appenders
28 | appenders = console, DRFA
29 |
30 | # console appender
31 | appender.console.type = Console
32 | appender.console.name = console
33 | appender.console.target = SYSTEM_ERR
34 | appender.console.layout.type = PatternLayout
35 | appender.console.layout.pattern = %d{yy/MM/dd HH:mm:ss} [%t]: %p %c{2}: %m%n
36 |
37 | # daily rolling file appender
38 | appender.DRFA.type = RollingFile
39 | appender.DRFA.name = DRFA
40 | appender.DRFA.fileName = ${sys:hive.log.dir}/${sys:hive.log.file}
41 | # Use %pid in the filePattern to append @ to the filename if you want separate log files for different CLI session
42 | appender.DRFA.filePattern = ${sys:hive.log.dir}/${sys:hive.log.file}.%d{yyyy-MM-dd}
43 | appender.DRFA.layout.type = PatternLayout
44 | appender.DRFA.layout.pattern = %d{ISO8601} %-5p [%t]: %c{2} (%F:%M(%L)) - %m%n
45 | appender.DRFA.policies.type = Policies
46 | appender.DRFA.policies.time.type = TimeBasedTriggeringPolicy
47 | appender.DRFA.policies.time.interval = 1
48 | appender.DRFA.policies.time.modulate = true
49 | appender.DRFA.strategy.type = DefaultRolloverStrategy
50 | appender.DRFA.strategy.max = 30
51 |
52 | # list of all loggers
53 | loggers = NIOServerCnxn, ClientCnxnSocketNIO, DataNucleus, Datastore, JPOX
54 |
55 | logger.NIOServerCnxn.name = org.apache.zookeeper.server.NIOServerCnxn
56 | logger.NIOServerCnxn.level = WARN
57 |
58 | logger.ClientCnxnSocketNIO.name = org.apache.zookeeper.ClientCnxnSocketNIO
59 | logger.ClientCnxnSocketNIO.level = WARN
60 |
61 | logger.DataNucleus.name = DataNucleus
62 | logger.DataNucleus.level = ERROR
63 |
64 | logger.Datastore.name = Datastore
65 | logger.Datastore.level = ERROR
66 |
67 | logger.JPOX.name = JPOX
68 | logger.JPOX.level = ERROR
69 |
70 | # root logger
71 | rootLogger.level = ${sys:hive.log.level}
72 | rootLogger.appenderRefs = root
73 | rootLogger.appenderRef.root.ref = ${sys:hive.root.logger}
74 |
--------------------------------------------------------------------------------
/test/docker/spark/conf/my.cnf:
--------------------------------------------------------------------------------
1 | [mysqld]
2 | bind-address=0.0.0.0
3 |
--------------------------------------------------------------------------------
/test/docker/spark/create_hive_tables.py:
--------------------------------------------------------------------------------
1 | import sys
2 | from pyspark import SparkContext, SparkConf, HiveContext, SQLContext
3 |
4 | if __name__ == '__main__':
5 | conf = SparkConf().setAppName("Plotly Exports")
6 | sc = SparkContext(conf=conf)
7 | hive_context = HiveContext(sc)
8 | print '=== Creating Database ==='
9 | hive_context.sql('CREATE DATABASE PLOTLY')
10 | hive_context.sql('USE PLOTLY')
11 |
12 | print '=== Creating Table ==='
13 | hive_context.sql("CREATE TABLE ALCOHOL_CONSUMPTION_BY_COUNTRY_2010 "
14 | "(LOCATION STRING, ALCOHOL FLOAT) ROW FORMAT "
15 | "DELIMITED FIELDS TERMINATED BY ',' "
16 | "TBLPROPERTIES (\"skip.header.line.count\"=\"1\")")
17 | print "=== loading data into table ==="
18 | hive_context.sql("LOAD DATA LOCAL INPATH "
19 | "'/plotly_datasets/2010_alcohol_consumption_by_country.csv' "
20 | "OVERWRITE INTO TABLE ALCOHOL_CONSUMPTION_BY_COUNTRY_2010")
21 | sys.exit()
22 |
--------------------------------------------------------------------------------
/test/docker/spark/setup_database.sh:
--------------------------------------------------------------------------------
1 | # Fetch Plotly master datasets to $HOME.
2 | cd && curl https://codeload.github.com/plotly/datasets/zip/master >| plotly_datasets.zip
3 |
4 | # unzip datasets:
5 | unzip plotly_datasets.zip
6 | mv datasets-master plotly_datasets
7 |
8 | # create table
9 | spark-submit /create_hive_tables.py
10 |
--------------------------------------------------------------------------------
/test/docker/spark/setup_livy.sh:
--------------------------------------------------------------------------------
1 |
2 | # Install pip and python dependencies to build livy:
3 | curl https://repos.fedorapeople.org/repos/dchen/apache-maven/epel-apache-maven.repo >| /etc/yum.repos.d/epel-apache-maven.repo
4 | sed -i s/\$releasever/6/g /etc/yum.repos.d/epel-apache-maven.repo
5 | yum -y install ant asciidoc apache-maven cyrus-sasl-devel cyrus-sasl-gssapi cyrus-sasl-plain gcc gcc-c++ git krb5-devel libffi-devel libxml2-devel libxslt-devel make mysql mysql-devel openldap-devel python-devel python-setuptools python-pip python-wheel sqlite-devel gmp-devel
6 |
7 | # Upgrade pip:
8 | pip install -U pip wheel setuptools
9 | pip install -r /requirements.txt
10 |
11 | # Get livy:
12 | git clone https://github.com/cloudera/livy.git
13 | cd livy
14 | export MAVEN_OPTS="-Xmx512m -XX:MaxPermSize=128m"
15 | mvn -DskipTests clean package
16 |
17 | # Setup livy logging directory
18 | mkdir -p /livy/logs
19 |
--------------------------------------------------------------------------------
/test/docker/spark/setup_mysql.sh:
--------------------------------------------------------------------------------
1 | sudo apt-get update
2 | export DEBIAN_FRONTEND=noninteractive
3 | apt-get -y install mysql-server mysql-client libmysql-java
4 |
5 | ln -s /usr/share/java/mysql.jar $HIVE_HOME/lib;
6 | service mysql restart
7 | mysql -e'CREATE DATABASE hive;'
8 | mysql -e'use hive;'
9 | # mysql -e'SOURCE $HIVE_HOME/scripts/metastore/upgrade/mysql/hive-schema-2.2.0.mysql.sql;'
10 | mysql -e'GRANT ALL PRIVILEGES ON *.* TO hive@"%" IDENTIFIED BY "hive"; flush privileges;';
11 | mysql -e'GRANT ALL PRIVILEGES ON *.* TO hive@"localhost" IDENTIFIED BY "hive"; flush privileges;';
12 | # Fix any conflicting host tables:
13 | mysql -e'use mysql; repair table host use_frm;';
14 | service mysql restart
15 | /hive/bin/schematool -dbType mysql -initSchema -verbose;
16 |
--------------------------------------------------------------------------------
/test/integration_test.js:
--------------------------------------------------------------------------------
1 | import chromedriver from 'chromedriver';
2 | import webdriver from 'selenium-webdriver';
3 | import electronPath from 'electron';
4 |
5 | import fs from 'fs';
6 | import fsExtra from 'fs-extra';
7 | import path from 'path';
8 |
9 | import * as settings from '../backend/settings.js';
10 | import {createStoragePath} from '../backend/utils/homeFiles';
11 |
12 | const delay = time => new Promise(resolve => setTimeout(resolve, time));
13 |
14 | // initialize credentials
15 | clearConnectorFolder();
16 | saveCredentials();
17 |
18 | // start chromedriver
19 | chromedriver.start();
20 | process.on('exit', chromedriver.stop);
21 |
22 | // Suppressing ESLint cause Mocha ensures `this` is bound in test functions
23 | /* eslint-disable no-invalid-this */
24 | describe('plotly database connector', function() {
25 | this.timeout(10 * 60 * 1000);
26 |
27 | before(() =>
28 | // wait for chromedriver to start up
29 | delay(1 * 1000)
30 | .then(() => {
31 | // start electron app
32 | this.driver = new webdriver.Builder()
33 | .usingServer('http://localhost:9515')
34 | .withCapabilities({
35 | chromeOptions: {
36 | binary: electronPath,
37 | args: [`app=${path.resolve()}`]
38 | }
39 | })
40 | .forBrowser('electron')
41 | .build();
42 |
43 | // wait for electron app to start up
44 | return delay(3 * 1000);
45 | })
46 | // ensure the electron app has loaded
47 | .then(() => this.driver.wait(webdriver.until.titleContains('Falcon')))
48 | );
49 |
50 | after(() =>
51 | Promise.resolve()
52 | .then(() => this.driver && this.driver.quit())
53 | .then(() => chromedriver.stop())
54 | );
55 | });
56 |
57 | function saveCredentials() {
58 | const usersValue = JSON.stringify([{
59 | 'username': 'plotly-database-connector',
60 | 'accessToken': '2MiYq1Oive6RRjC6y9D4u7DjlXSs7k'
61 | }]);
62 | if (!fs.existsSync(settings.getSetting('STORAGE_PATH'))) {
63 | createStoragePath();
64 | }
65 | fs.writeFileSync(
66 | settings.getSetting('SETTINGS_PATH'),
67 | `USERS: ${usersValue}`
68 | );
69 | }
70 |
71 | function clearConnectorFolder() {
72 | try {
73 | fsExtra.removeSync(settings.getSetting('STORAGE_PATH'));
74 | } catch (e) {
75 | console.warn(e); // eslint-disable-line no-console
76 | }
77 | }
78 | /* eslint-enable no-invalid-this */
79 |
--------------------------------------------------------------------------------
/webpack.config.base.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 |
3 | export default {
4 | module: {
5 | noParse: [/alasql/],
6 | rules: [{
7 | test: /\.jsx?$/,
8 | use: [{
9 | loader: 'babel-loader'
10 | }],
11 | exclude: /node_modules/
12 | }, {
13 | test: /\.css$/,
14 | use: [{
15 | loader: 'style-loader'
16 | }, {
17 | loader: 'css-loader'
18 | }]
19 | }]
20 | },
21 | output: {
22 | path: path.join(__dirname, 'dist'),
23 | filename: 'bundle.js',
24 | libraryTarget: 'commonjs2'
25 | },
26 | resolve: {
27 | extensions: ['.js', '.jsx', '.json']
28 | },
29 | plugins: [
30 | ],
31 | externals: [
32 | {
33 | 'csv-parse': 'commonjs csv-parse',
34 | 'data-urls': 'commonjs data-urls',
35 | 'dtrace-provider': 'commonjs dtrace-provider',
36 | 'font-awesome': 'font-awesome',
37 | 'ibm_db': 'commonjs ibm_db',
38 | 'mysql': 'mysql',
39 | 'oracledb': 'commonjs oracledb',
40 | 'pg': 'pg',
41 | 'pg-hstore': 'pg-hstore',
42 | 'restify': 'commonjs restify',
43 | 'sequelize': 'commonjs sequelize',
44 | 'source-map-support': 'source-map-support',
45 | 'sqlite3': 'sqlite3',
46 | 'tedious': 'tedious',
47 | 'whatwg-encoding': 'commonjs whatwg-encoding'
48 | }
49 | ]
50 | };
51 |
--------------------------------------------------------------------------------
/webpack.config.electron.js:
--------------------------------------------------------------------------------
1 | import webpack from 'webpack';
2 | import baseConfig from './webpack.config.base';
3 | import UglifyJsPlugin from 'uglifyjs-webpack-plugin';
4 |
5 | export default {
6 | ...baseConfig,
7 |
8 | devtool: 'source-map',
9 |
10 | entry: './backend/main.development',
11 |
12 | output: {
13 | path: __dirname,
14 | filename: './backend/main.js'
15 | },
16 |
17 | plugins: [
18 | ...baseConfig.plugins,
19 |
20 | new UglifyJsPlugin({
21 | sourceMap: false
22 | }),
23 | new webpack.BannerPlugin(
24 | {banner: 'require("source-map-support").install();',
25 | raw: true, entryOnly: false }
26 | ),
27 | new webpack.DefinePlugin({
28 | __DEV__: false,
29 | 'process.env.NODE_ENV': JSON.stringify('production')
30 | }),
31 | // https://github.com/felixge/node-formidable/issues/337
32 | new webpack.DefinePlugin({ 'global.GENTLY': false }),
33 |
34 | new webpack.LoaderOptionsPlugin({
35 | minimize: true
36 | })
37 | ],
38 |
39 | target: 'electron-main',
40 |
41 | node: {
42 | __dirname: false,
43 | __filename: false
44 | }
45 | };
46 |
--------------------------------------------------------------------------------
/webpack.config.headless.js:
--------------------------------------------------------------------------------
1 | import webpack from 'webpack';
2 | import baseConfig from './webpack.config.base';
3 | import UglifyJsPlugin from 'uglifyjs-webpack-plugin';
4 |
5 | export default {
6 | ...baseConfig,
7 |
8 | devtool: 'source-map',
9 |
10 | entry: './backend/headless.development.js',
11 |
12 | output: {
13 | path: __dirname,
14 | filename: './dist/headless-bundle.js'
15 | },
16 |
17 | plugins: [
18 | ...baseConfig.plugins,
19 |
20 | new UglifyJsPlugin({
21 | sourceMap: false
22 | }),
23 | new webpack.BannerPlugin(
24 | {banner: 'require("source-map-support").install();',
25 | raw: true, entryOnly: false }
26 | ),
27 | new webpack.DefinePlugin({
28 | 'process.env.NODE_ENV': JSON.stringify('production')
29 | }),
30 | // https://github.com/felixge/node-formidable/issues/337
31 | new webpack.DefinePlugin({ 'global.GENTLY': false })
32 | ],
33 |
34 | target: 'electron',
35 |
36 | node: {
37 | __dirname: false,
38 | __filename: false
39 | }
40 | };
41 |
--------------------------------------------------------------------------------
/webpack.config.production.js:
--------------------------------------------------------------------------------
1 | import webpack from 'webpack';
2 | import baseConfig from './webpack.config.base';
3 |
4 | const config = {
5 | ...baseConfig,
6 |
7 | devtool: 'source-map',
8 |
9 | entry: './app/index',
10 |
11 | output: {
12 | ...baseConfig.output,
13 |
14 | publicPath: '../dist/'
15 | },
16 |
17 | plugins: [
18 | ...baseConfig.plugins,
19 |
20 | new webpack.DefinePlugin({
21 | __DEV__: false,
22 | 'process.env.NODE_ENV': JSON.stringify('production')
23 | })
24 | ],
25 |
26 | target: 'electron-renderer'
27 | };
28 |
29 | export default config;
30 |
--------------------------------------------------------------------------------
/webpack.config.web.js:
--------------------------------------------------------------------------------
1 | import webpack from 'webpack';
2 | import baseConfig from './webpack.config.base';
3 | import path from 'path';
4 |
5 | const config = {
6 | ...baseConfig,
7 |
8 | devtool: 'source-map',
9 |
10 | entry: {
11 | 'web': './app/index'
12 | },
13 |
14 | output: {
15 | ...baseConfig.output,
16 | path: path.join(__dirname, 'static'),
17 | filename: '[name]-bundle.min.js',
18 | libraryTarget: 'var'
19 | },
20 |
21 | plugins: [
22 | ...baseConfig.plugins,
23 | new webpack.DefinePlugin({
24 | __DEV__: false,
25 | 'process.env.NODE_ENV': JSON.stringify('production')
26 | })
27 | ],
28 |
29 | target: 'web'
30 | };
31 |
32 | export default config;
33 |
--------------------------------------------------------------------------------