diff --git a/.env.example b/.env.example index 4a01bf885a..ad6adc3110 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,6 @@ TOOLJET_HOST=http://localhost:8082 -ENCRYPTION_SERVICE_SALT=replace_with_salt +LOCKBOX_MASTER_KEY=replace_with_encryption_key SECRET_KEY_BASE=replace_with_secret_key_base diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000000..265f113105 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,248 @@ +require: rubocop-rails +AllCops: + SuggestExtensions: false + TargetRubyVersion: 2.7 + # RuboCop has a bunch of cops enabled by default. This setting tells RuboCop + # to ignore them, so only the ones explicitly set in this file are enabled. + DisabledByDefault: true + Exclude: + - "**/templates/**/*" + - "**/vendor/**/*" + - "actionpack/lib/action_dispatch/journey/parser.rb" + - "db/schema.rb" + - "db/migrate/**/*" + - "node_modules/**/*" + - "bin/**" + - "config/application.rb" + - "config/boot.rb" + - "config/environment.rb" + - "config/environments/*.rb" + - "config/routes.rb" + +Rails/EnvironmentVariableAccess: + Enabled: false + +Rails/TimeZoneAssignment: + Enabled: true + +# Prefer &&/|| over and/or. +Style/AndOr: + Enabled: true + +# Align `when` with `case`. +Layout/CaseIndentation: + Enabled: true + +# Align comments with method definitions. +Layout/CommentIndentation: + Enabled: true + +Layout/EmptyLineAfterMagicComment: + Enabled: true + +# In a regular class definition, no empty lines around the body. +Layout/EmptyLinesAroundClassBody: + Enabled: true + +# In a regular method definition, no empty lines around the body. +Layout/EmptyLinesAroundMethodBody: + Enabled: true + +# In a regular module definition, no empty lines around the body. +Layout/EmptyLinesAroundModuleBody: + Enabled: true + +Layout/FirstArgumentIndentation: + Enabled: true + +# Use Ruby >= 1.9 syntax for hashes. Prefer { a: :b } over { :a => :b }. +Style/HashSyntax: + Enabled: true + +# Method definitions after `private` or `protected` isolated calls need one +# extra level of indentation. +Layout/IndentationConsistency: + Enabled: true + EnforcedStyle: indented_internal_methods + +# Two spaces, no tabs (for indentation). +Layout/IndentationWidth: + Enabled: true + +Layout/LeadingCommentSpace: + Enabled: true + +Layout/SpaceAfterColon: + Enabled: true + +Layout/SpaceAfterComma: + Enabled: true + +Layout/SpaceAroundEqualsInParameterDefault: + Enabled: true + +Layout/SpaceAroundKeyword: + Enabled: true + +# If we enable it then following code indentation fails +# @current_row_project_name = row["Project"] +# @current_row_date = row["Date"] +# @current_row_email = row["Email"] +# @current_row_client_name = row["Client"] +# @current_row_task_name = row["Task"] +# @current_row_notes = row["Notes"] +# @current_row_hours = row["Hours"] +Layout/SpaceAroundOperators: + Enabled: false + +Layout/SpaceBeforeComma: + Enabled: true + +Layout/SpaceBeforeFirstArg: + Enabled: true + +Style/DefWithParentheses: + Enabled: true + +# Defining a method with parameters needs parentheses. +Style/MethodDefParentheses: + Enabled: true + +Style/FrozenStringLiteralComment: + Enabled: true + EnforcedStyle: always + +# Use `foo {}` not `foo{}`. +Layout/SpaceBeforeBlockBraces: + Enabled: true + +# Use `foo { bar }` not `foo {bar}`. +Layout/SpaceInsideBlockBraces: + Enabled: true + +# Use `{ a: 1 }` not `{a:1}`. +Layout/SpaceInsideHashLiteralBraces: + Enabled: true + +Layout/SpaceInsideParens: + Enabled: true + +# Check quotes usage according to lint rule below. +Style/StringLiterals: + Enabled: true + EnforcedStyle: double_quotes + +# This cop looks for trailing blank lines and a final newline in the source code. +# This is being disabled because the requirement to have a final newline is illogical. +Layout/TrailingEmptyLines: + Enabled: false + +# No trailing whitespace. +Layout/TrailingWhitespace: + Enabled: true + +# Use quotes for string literals when they are enough. +Style/RedundantPercentQ: + Enabled: true + +# Align `end` with the matching keyword or starting expression except for +# assignments, where it should be aligned with the LHS. +Layout/EndAlignment: + Enabled: true + EnforcedStyleAlignWith: variable + +# Use my_method(my_arg) not my_method( my_arg ) or my_method my_arg. +Lint/RequireParentheses: + Enabled: true + +Style/RedundantReturn: + Enabled: true + AllowMultipleReturnValues: true + +Style/Semicolon: + Enabled: true + AllowAsExpressionSeparator: true + +# Corrects usage of :true/:false to true/false +Lint/BooleanSymbol: + Enabled: true + +# No space in method name and the arguments +Lint/ParenthesesAsGroupedExpression: + Enabled: true + +# Enabled Rails cops for the command rubocop -a +Rails: + Enabled: true + +# Correct usage of Date methods in Rails +Rails/Date: + Enabled: true + +# Correct usage of TimeZone methods in Rails +Rails/TimeZone: + Enabled: true + +Rails/ActiveRecordCallbacksOrder: + Enabled: true + +Rails/FindById: + Enabled: true + +Rails/Inquiry: + Enabled: false + +Rails/MailerName: + Enabled: true + +Rails/MatchRoute: + Enabled: true + +Rails/NegateInclude: + Enabled: true + +Rails/Pluck: + Enabled: true + +Rails/PluckInWhere: + Enabled: true + +Rails/RenderInline: + Enabled: true + +Rails/RenderPlainText: + Enabled: true + +Rails/ShortI18n: + Enabled: true + +Rails/WhereExists: + Enabled: true + +Rails/AfterCommitOverride: + Enabled: true + +Rails/SquishedSQLHeredocs: + Enabled: true + +Rails/WhereNot: + Enabled: true + +Rails/SkipsModelValidations: + Enabled: false + +Rails/AttributeDefaultBlockValue: # (new in 2.9) + Enabled: true +Rails/WhereEquals: # (new in 2.9) + Enabled: false + +Rails/HelperInstanceVariable: + Enabled: false + +Rails/UnknownEnv: + Environments: + - production + - development + - test + - staging + - heroku diff --git a/Gemfile b/Gemfile index 8d34326725..e29e04cee7 100644 --- a/Gemfile +++ b/Gemfile @@ -38,12 +38,14 @@ gem 'typhoeus' gem "mongo", "~> 2" gem 'aws-sdk', '~> 3' gem 'kaminari' +gem 'lockbox' group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platforms: %i[mri mingw x64_mingw] gem 'dotenv-rails' gem 'rubocop', require: false + gem 'rubocop-rails' end group :development do diff --git a/Gemfile.lock b/Gemfile.lock index 4d17c0dae5..63a944e78f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1238,6 +1238,7 @@ GEM listen (3.5.1) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) + lockbox (0.6.4) lograge (0.11.2) actionpack (>= 4) activesupport (>= 4) @@ -1270,7 +1271,7 @@ GEM racc (~> 1.4) os (1.1.1) parallel (1.20.1) - parser (3.0.1.0) + parser (3.0.1.1) ast (~> 2.4.1) pg (1.2.3) public_suffix (4.0.6) @@ -1321,17 +1322,21 @@ GEM request_store (1.5.0) rack (>= 1.4) rexml (3.2.5) - rubocop (1.13.0) + rubocop (1.15.0) parallel (~> 1.10) parser (>= 3.0.0.0) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml - rubocop-ast (>= 1.2.0, < 2.0) + rubocop-ast (>= 1.5.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.4.1) - parser (>= 2.7.1.5) + rubocop-ast (1.7.0) + parser (>= 3.0.1.1) + rubocop-rails (2.10.1) + activesupport (>= 4.2.0) + rack (>= 1.1) + rubocop (>= 1.7.0, < 2.0) ruby-progressbar (1.11.0) ruby2_keywords (0.0.4) signet (0.15.0) @@ -1375,6 +1380,7 @@ DEPENDENCIES jwt kaminari listen (~> 3.3) + lockbox lograge mongo (~> 2) mysql2 @@ -1385,6 +1391,7 @@ DEPENDENCIES rails (~> 6.1.3, >= 6.1.3.1) redis rubocop + rubocop-rails simple_command spring typhoeus diff --git a/app.json b/app.json index 7348f800de..98b335a3d3 100644 --- a/app.json +++ b/app.json @@ -18,8 +18,8 @@ "description": "Public URL of ToolJet installtion.", "value": "https://app.tooljet.io" }, - "ENCRYPTION_SERVICE_SALT": { - "description": "Salt for encrypting datasource credentials.", + "LOCKBOX_MASTER_KEY": { + "description": "Key for encrypting datasource credentials.", "value": "" }, "SECRET_KEY_BASE": { diff --git a/app/controllers/app_users_controller.rb b/app/controllers/app_users_controller.rb index c3031e773d..a6b04b18e0 100644 --- a/app/controllers/app_users_controller.rb +++ b/app/controllers/app_users_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class AppUsersController < ApplicationController def create org_user_id = params[:org_user_id] @@ -18,7 +20,7 @@ class AppUsersController < ApplicationController if app_user.save render json: { success: true } else - render json: { message: 'Could not create user' }, status: 500 + render json: { message: "Could not create user" }, status: :internal_server_error end end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index f7e8eba59b..67ac9b663e 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class ApplicationController < ActionController::API include Pundit rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized @@ -7,12 +9,12 @@ class ApplicationController < ActionController::API private - def authenticate_request - @current_user = AuthorizeApiRequest.call(request.headers).result - render json: { error: 'Not Authorized' }, status: 401 unless @current_user - end + def authenticate_request + @current_user = AuthorizeApiRequest.call(request.headers).result + render json: { error: "Not Authorized" }, status: :unauthorized unless @current_user + end - def user_not_authorized - render json: { error: 'Access denied' }, status: :forbidden - end + def user_not_authorized + render json: { error: "Access denied" }, status: :forbidden + end end diff --git a/app/controllers/apps_controller.rb b/app/controllers/apps_controller.rb index 1c2d73f380..7fd086beec 100644 --- a/app/controllers/apps_controller.rb +++ b/app/controllers/apps_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class AppsController < ApplicationController skip_before_action :authenticate_request, only: [:show] @@ -13,44 +15,44 @@ class AppsController < ApplicationController @scope = @folder.apps end - @apps = @scope.order('created_at desc') + @apps = @scope.order("created_at desc") .page(params[:page]) .per(10) .includes(:user) - @meta = { + @meta = { total_pages: @apps.total_pages, folder_count: @scope.count, total_count: App.where(organization: @current_user.organization).count, - current_page: @apps.current_page - } + current_page: @apps.current_page + } end def create authorize App @app = App.create({ - name: 'Untitled app', + name: "Untitled app", organization: @current_user.organization, - current_version: AppVersion.new(name: 'v0'), + current_version: AppVersion.new(name: "v0"), user: @current_user }) - AppUser.create(app: @app, user: @current_user, role: 'admin') + AppUser.create(app: @app, user: @current_user, role: "admin") end def show - @app = App.find params[:id] + @app = App.find params[:id] - # Logic to bypass auth for public apps - unless @app.is_public - authenticate_request - authorize @app - end + # Logic to bypass auth for public apps + unless @app.is_public + authenticate_request + authorize @app + end end def update @app = App.find params[:id] authorize @app - @app.update(params['app'].permit('name', 'current_version_id', 'is_public')) + @app.update(params["app"].permit("name", "current_version_id", "is_public")) end def users diff --git a/app/controllers/authentication_controller.rb b/app/controllers/authentication_controller.rb index 548837d622..a031cdb1b7 100644 --- a/app/controllers/authentication_controller.rb +++ b/app/controllers/authentication_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class AuthenticationController < ApplicationController skip_before_action :authenticate_request @@ -5,7 +7,7 @@ class AuthenticationController < ApplicationController command = AuthenticateUser.call(params[:email], params[:password]) if command.success? - user = User.find_by_email params[:email] + user = User.find_by email: params[:email] render json: { auth_token: command.result, first_name: user.first_name, last_name: user.last_name, email: user.email } else @@ -15,15 +17,15 @@ class AuthenticationController < ApplicationController def signup # Check if the installation allows user signups - if(ENV['DISABLE_SIGNUPS'] === "true") - render json: {}, status: 500 + if (ENV["DISABLE_SIGNUPS"] === "true") + render json: {}, status: :internal_server_error else email = params[:email] password = SecureRandom.uuid - org = Organization.create(name: 'new org') + org = Organization.create(name: "new org") user = User.create(email: email, password: password, organization: org, invitation_token: SecureRandom.uuid) - org_user = OrganizationUser.create(user: user, organization: org, role: 'admin') + org_user = OrganizationUser.create(user_id: user.id, organization_id: org.id, role: "admin") # UserMailer.with(user: user, sender: @current_user).new_signup_email.deliver if org_user.save end diff --git a/app/controllers/data_queries_controller.rb b/app/controllers/data_queries_controller.rb index 35b6ee2493..86ebaf3721 100644 --- a/app/controllers/data_queries_controller.rb +++ b/app/controllers/data_queries_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class DataQueriesController < ApplicationController skip_before_action :authenticate_request, only: [:run] @@ -15,11 +17,10 @@ class DataQueriesController < ApplicationController ) if @data_query.errors.present? - render json: { message: 'Query could not be created' }, status: 500 + render json: { message: "Query could not be created" }, status: :internal_server_error else - render json: { message: 'success' } + render json: { message: "success" } end - end def update @@ -27,9 +28,9 @@ class DataQueriesController < ApplicationController @data_query.update(options: params[:options], name: params[:name]) if @data_query.errors.present? - render json: { message: 'Query could not be updated' }, status: 500 - else - render json: { message: 'success' } + render json: { message: "Query could not be updated" }, status: :internal_server_error + else + render json: { message: "success" } end end diff --git a/app/controllers/data_sources_controller.rb b/app/controllers/data_sources_controller.rb index 06e7167388..f3cc43dc82 100644 --- a/app/controllers/data_sources_controller.rb +++ b/app/controllers/data_sources_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class DataSourcesController < ApplicationController def index @data_sources = DataSource.where(app_id: params[:app_id]) @@ -8,17 +10,17 @@ class DataSourcesController < ApplicationController options_to_save = {} options.each do |option| - if option['encrypted'] - credential = Credential.create(value: option['value']) + if option["encrypted"] + credential = Credential.create(value: option["value"]) - options_to_save[option['key']] = { + options_to_save[option["key"]] = { credential_id: credential.id, - encrypted: option['encrypted'] + encrypted: option["encrypted"] } else - options_to_save[option['key']] = { - value: option['value'], - encrypted: option['encrypted'] + options_to_save[option["key"]] = { + value: option["value"], + encrypted: option["encrypted"] } end end @@ -38,17 +40,17 @@ class DataSourcesController < ApplicationController options_to_save = {} options.each do |option| - if option['encrypted'] - credential = Credential.create(value: option['value']) + if option["encrypted"] + credential = Credential.create(value: option["value"]) - options_to_save[option['key']] = { + options_to_save[option["key"]] = { credential_id: credential.id, - encrypted: option['encrypted'] + encrypted: option["encrypted"] } else - options_to_save[option['key']] = { - value: option['value'], - encrypted: option['encrypted'] + options_to_save[option["key"]] = { + value: option["value"], + encrypted: option["encrypted"] } end end @@ -67,27 +69,27 @@ class DataSourcesController < ApplicationController render json: { status: 200 } rescue StandardError => e puts e - render json: { message: e }, status: 500 + render json: { message: e }, status: :internal_server_error end def authorize_oauth2 data_source = DataSource.find params[:data_source_id] options = CredentialService.new.decrypt_options(data_source.options) - access_token_url = options['access_token_url'] + access_token_url = options["access_token_url"] - custom_params = options['custom_auth_params'].to_h + custom_params = options["custom_auth_params"].to_h response = HTTParty.post(access_token_url, body: { code: params[:code], - client_id: options['client_id'], - client_secret: options['client_secret'], - grant_type: options['grant_type'], + client_id: options["client_id"], + client_secret: options["client_secret"], + grant_type: options["grant_type"], redirect_uri: "#{ENV.fetch('TOOLJET_HOST')}/oauth2/authorize", **custom_params }.to_json, - headers: { 'Content-Type' => 'application/json' }) + headers: { "Content-Type" => "application/json" }) result = JSON.parse(response.body) - access_token = result['access_token'] + access_token = result["access_token"] options = { access_token: access_token } @@ -108,20 +110,20 @@ class DataSourcesController < ApplicationController render json: { url: url } end - private - def fetch_oauth_options(options) + private + def fetch_oauth_options(options) # Fetch necessary access token if OAuth2 based data source - if options.find { |option| option['key'] == 'oauth2' } - provider = options.find { |option| option['key'] === 'provider' } ['value'] + if options.find { |option| option["key"] == "oauth2" } + provider = options.find { |option| option["key"] === "provider" } ["value"] service_class = "#{provider.capitalize}OauthService".constantize - access_info = service_class.fetch_access_token(options.find { |option| option['key'] === 'code' } ['value']) - options.reject! { |option| option['key'] == 'code' } + access_info = service_class.fetch_access_token(options.find { |option| option["key"] === "code" } ["value"]) + options.reject! { |option| option["key"] == "code" } access_info.each do |info| option = {} - option['key'] = info[0] - option['value'] = info[1] - option['encrypted'] = true + option["key"] = info[0] + option["value"] = info[1] + option["encrypted"] = true options << option end end diff --git a/app/controllers/folder_apps_controller.rb b/app/controllers/folder_apps_controller.rb index 3a80097cf8..b36a5525ed 100644 --- a/app/controllers/folder_apps_controller.rb +++ b/app/controllers/folder_apps_controller.rb @@ -1,22 +1,23 @@ +# frozen_string_literal: true + class FolderAppsController < ApplicationController + def create + app_id = params[:app_id] + folder_id = params[:folder_id] - def create - app_id = params[:app_id] - folder_id = params[:folder_id] - - @app = App.find app_id + @app = App.find app_id - unless AppPolicy.new(@current_user, @app).update? - render json: { message: 'Could not add app to folder due to insufficient permissions' }, status: 500 - return - end - - folder_app = FolderApp.new(app_id: app_id, folder_id: folder_id) - - if folder_app.save - render json: {} - else - render json: { message: 'App already in folder' }, status: 500 - end + unless AppPolicy.new(@current_user, @app).update? + render json: { message: "Could not add app to folder due to insufficient permissions" }, status: :internal_server_error + return end + + folder_app = FolderApp.new(app_id: app_id, folder_id: folder_id) + + if folder_app.save + render json: {} + else + render json: { message: "App already in folder" }, status: :internal_server_error + end + end end diff --git a/app/controllers/folders_controller.rb b/app/controllers/folders_controller.rb index fbb0bda159..85eac7852d 100644 --- a/app/controllers/folders_controller.rb +++ b/app/controllers/folders_controller.rb @@ -1,10 +1,11 @@ +# frozen_string_literal: true + class FoldersController < ApplicationController + def index + @folders = Folder.where(organization: @current_user.organization) + end - def index - @folders = Folder.where(organization: @current_user.organization) - end - - def create - Folder.create(name: params[:name], organization: @current_user.organization) - end + def create + Folder.create(name: params[:name], organization: @current_user.organization) + end end diff --git a/app/controllers/organization_users_controller.rb b/app/controllers/organization_users_controller.rb index 908c059c05..a347a37219 100644 --- a/app/controllers/organization_users_controller.rb +++ b/app/controllers/organization_users_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class OrganizationUsersController < ApplicationController def create authorize OrganizationUser diff --git a/app/controllers/organizations_controller.rb b/app/controllers/organizations_controller.rb index 8cd6f6efe9..1efeb0ee0c 100644 --- a/app/controllers/organizations_controller.rb +++ b/app/controllers/organizations_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class OrganizationsController < ApplicationController def users @org_users = OrganizationUser.where(organization: @current_user.organization).includes(:user) diff --git a/app/controllers/probe_controller.rb b/app/controllers/probe_controller.rb index 1188d91895..68bf165ed8 100644 --- a/app/controllers/probe_controller.rb +++ b/app/controllers/probe_controller.rb @@ -1,7 +1,9 @@ +# frozen_string_literal: true + class ProbeController < ApplicationController skip_before_action :authenticate_request def health_check - render json: { works: 'yeah' } + render json: { works: "yeah" } end end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index e7083933a6..329f56f891 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class UsersController < ApplicationController skip_before_action :authenticate_request @@ -6,13 +8,13 @@ class UsersController < ApplicationController if user user.update(first_name: params[:first_name], last_name: params[:last_name], password: params[:password], invitation_token: nil) - user.organization_users.first.update(status: 'active') + user.organization_users.first.update(status: "active") if params[:new_signup] user.organization.update(name: params[:organization]) end else - render json: { message: 'Invalid Invitation Token' }, status: :bad_request + render json: { message: "Invalid Invitation Token" }, status: :bad_request end end end diff --git a/app/controllers/versions_controller.rb b/app/controllers/versions_controller.rb index 8a1aca4b04..d21dc224e9 100644 --- a/app/controllers/versions_controller.rb +++ b/app/controllers/versions_controller.rb @@ -1,12 +1,14 @@ +# frozen_string_literal: true + class VersionsController < ApplicationController def create @app = App.find params[:app_id] - name = params[:version]['versionName'] + name = params[:version]["versionName"] AppVersion.create(app: @app, name: name) end def index - @versions = AppVersion.where(app_id: params['app_id']).order('created_at desc') + @versions = AppVersion.where(app_id: params["app_id"]).order("created_at desc") end def update diff --git a/app/models/app.rb b/app/models/app.rb index c2cb9bde14..f38e633216 100644 --- a/app/models/app.rb +++ b/app/models/app.rb @@ -1,8 +1,10 @@ +# frozen_string_literal: true + class App < ApplicationRecord belongs_to :organization has_many :data_queries has_many :app_users has_many :app_versions - belongs_to :current_version, class_name: 'AppVersion', optional: true + belongs_to :current_version, class_name: "AppVersion", optional: true belongs_to :user, optional: true end diff --git a/app/models/app_user.rb b/app/models/app_user.rb index dd828bfba8..1497d84e11 100644 --- a/app/models/app_user.rb +++ b/app/models/app_user.rb @@ -1,17 +1,19 @@ +# frozen_string_literal: true + class AppUser < ApplicationRecord belongs_to :app belongs_to :user - validates_uniqueness_of :app_id, scope: [:user_id] + validates :app_id, uniqueness: { scope: [:user_id] } def admin? - role == 'admin' + role == "admin" end def developer? - role == 'developer' + role == "developer" end def viewer? - role == 'viewer' + role == "viewer" end end diff --git a/app/models/app_version.rb b/app/models/app_version.rb index d7b4b28edb..e263b610e0 100644 --- a/app/models/app_version.rb +++ b/app/models/app_version.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class AppVersion < ApplicationRecord belongs_to :app end diff --git a/app/models/application_record.rb b/app/models/application_record.rb index 10a4cba84d..71fbba5b32 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class ApplicationRecord < ActiveRecord::Base self.abstract_class = true end diff --git a/app/models/credential.rb b/app/models/credential.rb index 881f480596..f605f420e0 100644 --- a/app/models/credential.rb +++ b/app/models/credential.rb @@ -1,5 +1,5 @@ -class Credential < ApplicationRecord - include Encryptable +# frozen_string_literal: true - attr_encrypted :value +class Credential < ApplicationRecord + encrypts :value end diff --git a/app/models/data_query.rb b/app/models/data_query.rb index 7e0200d113..cfbe886764 100644 --- a/app/models/data_query.rb +++ b/app/models/data_query.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + class DataQuery < ApplicationRecord belongs_to :app belongs_to :data_source, optional: true - validates_uniqueness_of :name, scope: [:app_id] + validates :name, uniqueness: { scope: [:app_id] } end diff --git a/app/models/data_source.rb b/app/models/data_source.rb index 314a180f0a..adbcb35bf0 100644 --- a/app/models/data_source.rb +++ b/app/models/data_source.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class DataSource < ApplicationRecord belongs_to :app has_many :data_queries diff --git a/app/models/data_source_user_oauth2.rb b/app/models/data_source_user_oauth2.rb index acb0802a3d..17b25f9e61 100644 --- a/app/models/data_source_user_oauth2.rb +++ b/app/models/data_source_user_oauth2.rb @@ -1,8 +1,8 @@ -class DataSourceUserOauth2 < ApplicationRecord - include Encryptable +# frozen_string_literal: true +class DataSourceUserOauth2 < ApplicationRecord belongs_to :user belongs_to :data_source - attr_encrypted :options + encrypts :options end diff --git a/app/models/folder.rb b/app/models/folder.rb index dcb9b34bc0..26c9cc6026 100644 --- a/app/models/folder.rb +++ b/app/models/folder.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + class Folder < ApplicationRecord belongs_to :organization has_many :folder_apps - has_many :apps, :through => :folder_apps + has_many :apps, through: :folder_apps end diff --git a/app/models/folder_app.rb b/app/models/folder_app.rb index c11ad7e671..f40fb36c13 100644 --- a/app/models/folder_app.rb +++ b/app/models/folder_app.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + class FolderApp < ApplicationRecord belongs_to :folder belongs_to :app - validates_uniqueness_of :app_id, scope: [:folder_id] + validates :app_id, uniqueness: { scope: [:folder_id] } end diff --git a/app/models/organization.rb b/app/models/organization.rb index 56cf9c7066..6161c412d3 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class Organization < ApplicationRecord has_many :users has_many :apps diff --git a/app/models/organization_user.rb b/app/models/organization_user.rb index d7b603fe3f..4ce4936689 100644 --- a/app/models/organization_user.rb +++ b/app/models/organization_user.rb @@ -1,16 +1,18 @@ +# frozen_string_literal: true + class OrganizationUser < ApplicationRecord belongs_to :organization belongs_to :user def admin? - role == 'admin' + role == "admin" end def developer? - role == 'developer' + role == "developer" end def viewer? - role == 'viewer' + role == "viewer" end end diff --git a/app/models/user.rb b/app/models/user.rb index 6813108df5..2e9bf3d236 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class User < ApplicationRecord has_secure_password has_many :organization_users diff --git a/app/services/airtable_query_service.rb b/app/services/airtable_query_service.rb new file mode 100644 index 0000000000..442b216880 --- /dev/null +++ b/app/services/airtable_query_service.rb @@ -0,0 +1,68 @@ +class AirtableQueryService + attr_accessor :query, :source, :options, :source_options, :current_user + + def initialize(data_query, data_source, options, source_options, current_user) + @query = data_query + @source = data_source + @options = options + @source_options = source_options + @current_user = current_user + end + + def process + operation = options['operation'] + api_key = source_options['api_key'] + error = false + + if operation === 'list_records' + + base_id = options['base_id'] + table_name = options['table_name'] + page_size = options['page_size'] + offset = options['offset'] + + result = list_records(api_key, base_id, table_name, page_size, offset) + + data = result + error = result.code != 200 + end + + if operation === 'retrieve_record' + + base_id = options['base_id'] + table_name = options['table_name'] + record_id = options['record_id'] + + result = retrieve_record(api_key, base_id, table_name, record_id) + + data = result + error = result.code != 200 + end + + if error + { status: 'error', code: 500, message: data["message"], data: data } + else + { status: 'success', data: data } + end + end + + private + + def list_records(api_key, base_id, table_name, page_size, offset) + + result = HTTParty.get(URI.encode("https://api.airtable.com/v0/#{base_id}/#{table_name}"), + headers: { 'Content-Type': + 'application/json', "Authorization": "Bearer #{api_key}" }) + + result + end + + def retrieve_record(api_key, base_id, table_name, record_id) + + result = HTTParty.get(URI.encode("https://api.airtable.com/v0/#{base_id}/#{table_name}/#{record_id}"), + headers: { 'Content-Type': + 'application/json', "Authorization": "Bearer #{api_key}" }) + + result + end +end diff --git a/app/services/elasticsearch_query_service.rb b/app/services/elasticsearch_query_service.rb index f332d7ff9f..e6ce06b701 100644 --- a/app/services/elasticsearch_query_service.rb +++ b/app/services/elasticsearch_query_service.rb @@ -13,10 +13,23 @@ class ElasticsearchQueryService end def self.connection options + + scheme = options.dig('scheme', 'value') + host = options.dig('host', 'value') + port = options.dig('port', 'value') + username = options.dig('username', 'value') + password = options.dig('password', 'value') + + unless username.blank? || password.blank? + url = "#{scheme}://#{username}:#{password}@#{host}:#{port}" + else + url = "#{scheme}://#{host}:#{port}" + end + client = Elasticsearch::Client.new( - url: "#{options.dig('host', 'value')}:#{options.dig('port', 'value')}", + url: url, retry_on_failure: 5, - request_timeout: 30, + request_timeout: 15, adapter: :typhoeus ) @@ -71,10 +84,23 @@ class ElasticsearchQueryService private def create_connection + + scheme = source_options['scheme'] + host = source_options['host'] + port = source_options['port'] + username = source_options['username'] + password = source_options['password'] + + unless username.blank? || password.blank? + url = "#{scheme}://#{username}:#{password}@#{host}:#{port}" + else + url = "#{scheme}://#{host}:#{port}" + end + connection = Elasticsearch::Client.new( - url: "#{source_options['host']}:#{source_options['port']}", + url: url, retry_on_failure: 5, - request_timeout: 30, + request_timeout: 15, adapter: :typhoeus ) diff --git a/app/services/encryption_service.rb b/app/services/encryption_service.rb deleted file mode 100644 index a639376769..0000000000 --- a/app/services/encryption_service.rb +++ /dev/null @@ -1,26 +0,0 @@ -class EncryptionService - KEY = ActiveSupport::KeyGenerator.new( - ENV.fetch('SECRET_KEY_BASE') - ).generate_key( - ENV.fetch('ENCRYPTION_SERVICE_SALT'), - ActiveSupport::MessageEncryptor.key_len - ).freeze - - private_constant :KEY - - delegate :encrypt_and_sign, :decrypt_and_verify, to: :encryptor - - def self.encrypt(value) - new.encrypt_and_sign(value) - end - - def self.decrypt(value) - new.decrypt_and_verify(value) - end - - private - - def encryptor - ActiveSupport::MessageEncryptor.new(KEY) - end -end diff --git a/app/services/postgresql_query_service.rb b/app/services/postgresql_query_service.rb index cb33c528a2..823505d8cc 100644 --- a/app/services/postgresql_query_service.rb +++ b/app/services/postgresql_query_service.rb @@ -39,7 +39,10 @@ class PostgresqlQueryService result = connection.exec(query_text) rescue StandardError => e - reset_connection(data_source) if connection.finished? + if connection&.status === PG::Constants::CONNECTION_BAD + connection&.finish + reset_connection(data_source) + end puts e error = { message: e.message } diff --git a/config/initializers/lockbox.rb b/config/initializers/lockbox.rb new file mode 100644 index 0000000000..368ac3a2d1 --- /dev/null +++ b/config/initializers/lockbox.rb @@ -0,0 +1 @@ +Lockbox.master_key = ENV.fetch('LOCKBOX_MASTER_KEY') diff --git a/db/migrate/20210529101316_add_cipher_text_to_credentials.rb b/db/migrate/20210529101316_add_cipher_text_to_credentials.rb new file mode 100644 index 0000000000..03d213f5d0 --- /dev/null +++ b/db/migrate/20210529101316_add_cipher_text_to_credentials.rb @@ -0,0 +1,5 @@ +class AddCipherTextToCredentials < ActiveRecord::Migration[6.1] + def change + add_column :credentials, :value_ciphertext, :text + end +end diff --git a/db/migrate/20210529140113_add_cipher_text_to_data_source_user_oauth2.rb b/db/migrate/20210529140113_add_cipher_text_to_data_source_user_oauth2.rb new file mode 100644 index 0000000000..e7e9984f01 --- /dev/null +++ b/db/migrate/20210529140113_add_cipher_text_to_data_source_user_oauth2.rb @@ -0,0 +1,5 @@ +class AddCipherTextToDataSourceUserOauth2 < ActiveRecord::Migration[6.1] + def change + add_column :data_source_user_oauth2s, :options_ciphertext, :text + end +end diff --git a/db/schema.rb b/db/schema.rb index 662c957c04..f57a864cda 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,14 +10,14 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2021_05_19_084741) do +ActiveRecord::Schema.define(version: 2021_05_29_140113) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" enable_extension "plpgsql" enable_extension "uuid-ossp" - create_table "app_users", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "app_users", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.uuid "app_id", null: false t.uuid "user_id", null: false t.string "role" @@ -27,7 +27,7 @@ ActiveRecord::Schema.define(version: 2021_05_19_084741) do t.index ["user_id"], name: "index_app_users_on_user_id" end - create_table "app_versions", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "app_versions", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.uuid "app_id", null: false t.string "name" t.json "definition" @@ -36,7 +36,7 @@ ActiveRecord::Schema.define(version: 2021_05_19_084741) do t.index ["app_id"], name: "index_app_versions_on_app_id" end - create_table "apps", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "apps", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.string "name" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false @@ -50,13 +50,14 @@ ActiveRecord::Schema.define(version: 2021_05_19_084741) do t.index ["user_id"], name: "index_apps_on_user_id" end - create_table "credentials", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "credentials", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.text "encrypted_value" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false + t.text "value_ciphertext" end - create_table "data_queries", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "data_queries", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.uuid "app_id", null: false t.string "name" t.json "options" @@ -68,17 +69,18 @@ ActiveRecord::Schema.define(version: 2021_05_19_084741) do t.index ["data_source_id"], name: "index_data_queries_on_data_source_id" end - create_table "data_source_user_oauth2s", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "data_source_user_oauth2s", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.uuid "user_id", null: false t.uuid "data_source_id", null: false t.text "encrypted_options" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false + t.text "options_ciphertext" t.index ["data_source_id"], name: "index_data_source_user_oauth2s_on_data_source_id" t.index ["user_id"], name: "index_data_source_user_oauth2s_on_user_id" end - create_table "data_sources", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "data_sources", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.uuid "app_id", null: false t.string "name" t.json "options" @@ -88,7 +90,7 @@ ActiveRecord::Schema.define(version: 2021_05_19_084741) do t.index ["app_id"], name: "index_data_sources_on_app_id" end - create_table "endpoints", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "endpoints", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.string "identifier" t.string "path" t.string "method" @@ -101,7 +103,7 @@ ActiveRecord::Schema.define(version: 2021_05_19_084741) do t.index ["integration_id"], name: "index_endpoints_on_integration_id" end - create_table "folder_apps", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "folder_apps", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.uuid "folder_id", null: false t.uuid "app_id", null: false t.datetime "created_at", precision: 6, null: false @@ -110,7 +112,7 @@ ActiveRecord::Schema.define(version: 2021_05_19_084741) do t.index ["folder_id"], name: "index_folder_apps_on_folder_id" end - create_table "folders", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "folders", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.string "name" t.uuid "organization_id", null: false t.datetime "created_at", precision: 6, null: false @@ -118,14 +120,14 @@ ActiveRecord::Schema.define(version: 2021_05_19_084741) do t.index ["organization_id"], name: "index_folders_on_organization_id" end - create_table "integrations", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "integrations", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.string "identifier" t.string "name" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false end - create_table "organization_users", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "organization_users", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.uuid "organization_id", null: false t.uuid "user_id", null: false t.string "role" @@ -136,14 +138,14 @@ ActiveRecord::Schema.define(version: 2021_05_19_084741) do t.index ["user_id"], name: "index_organization_users_on_user_id" end - create_table "organizations", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "organizations", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.string "name" t.string "domain" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false end - create_table "users", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "users", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.string "first_name" t.string "last_name" t.string "email" diff --git a/deploy/kubernetes/server-deployment.yaml b/deploy/kubernetes/server-deployment.yaml index 50edc3116a..d6bd3a091e 100644 --- a/deploy/kubernetes/server-deployment.yaml +++ b/deploy/kubernetes/server-deployment.yaml @@ -3,7 +3,7 @@ kind: Deployment metadata: name: tooljet-server-deployment spec: - replicas: 1 + replicas: 3 strategy: type: RollingUpdate rollingUpdate: @@ -24,11 +24,11 @@ spec: command: ["bundle", "exec", "rails", "s", "-p", "3000", "-b", "0.0.0.0"] resources: limits: - memory: "1024Mi" - cpu: "1000m" + memory: "3000Mi" + cpu: "500m" requests: - memory: "1024Mi" - cpu: "1000m" + memory: "3000Mi" + cpu: "500m" ports: - containerPort: 3000 readinessProbe: @@ -62,16 +62,16 @@ spec: secretKeyRef: name: server key: db + - name: LOCKBOX_MASTER_KEY + valueFrom: + secretKeyRef: + name: server + key: lockbox_key - name: SECRET_KEY_BASE valueFrom: secretKeyRef: name: server key: secret_key_base - - name: ENCRYPTION_SERVICE_SALT - valueFrom: - secretKeyRef: - name: server - key: encryption_salt - imagePullSecrets: + imagePullSecrets: - name: registry-secret \ No newline at end of file diff --git a/docs/docs/tutorial/adding-widget.md b/docs/docs/tutorial/adding-widget.md index 8ecff40b27..81936eaa19 100644 --- a/docs/docs/tutorial/adding-widget.md +++ b/docs/docs/tutorial/adding-widget.md @@ -5,8 +5,7 @@ sidebar_position: 5 # Adding a widget To add a widget, navigate to the `insert` tab of right sidebar. It will display the list of built-in widgets that can be added to the app. Use the search functionality to quickly find the widget that you want. - -ToolJet - widgets list +ToolJet - widgets list ## Drag and drop a widget Let's add a `table` widget to the app to show the customer data from the query that we created in the previous steps. @@ -32,4 +31,8 @@ Since we have already run the query in previous step, the data will be immedietl ToolJet - Table with data -So far in this tutorial, we have connected to a PostgreSQL database and displayed the data on a table. \ No newline at end of file +So far in this tutorial, we have connected to a PostgreSQL database and displayed the data on a table. + +:::tip +Read the widget reference of table [here](/docs/widgets/table) for more customizations such as server-side pagination, actions, editing data. +::: \ No newline at end of file diff --git a/docs/docs/widgets/chart.md b/docs/docs/widgets/chart.md new file mode 100644 index 0000000000..16fbfa56c5 --- /dev/null +++ b/docs/docs/widgets/chart.md @@ -0,0 +1,70 @@ +--- +sidebar_position: 1 +--- + +# Chart + +Chart widget takes the chart type, data and styles to draw charts using Plotly.js. + +Support chart types: +- Line charts +- Bar charts +- Pie charts + +## Line charts + +Data requirements: + +The data needs to be an array of objects and each object should have `x` and `y` keys. + +Example: + +``` +[ + { "x": 100, "y": "Jan"}, + { "x": 80, "y": "Feb"}, + { "x": 40, "y": "Mar"} +] +``` + +The chart will look like this: +ToolJet - line charts + +## Bar charts + +Data requirements: + +The data needs to be an array of objects and each object should have `x` and `y` keys. + +Example: + +``` +[ + { "x": 100, "y": "Jan"}, + { "x": 80, "y": "Feb"}, + { "x": 40, "y": "Mar"} +] +``` + +The chart will look like this: +ToolJet - line charts + + +## Pie charts + +Data requirements: + +The data needs to be an array of objects and each object should have `label` and `value` keys. + +Example: + +``` +[ + { "label": "Jan", "value": 100 }, + { "label": "Feb", "value": 80 }, + { "label": "Mar", "value": 20 } +] +``` + +The chart will look like this: +ToolJet - line charts diff --git a/docs/docs/widgets/table.md b/docs/docs/widgets/table.md index 09c8184888..568647f7b7 100644 --- a/docs/docs/widgets/table.md +++ b/docs/docs/widgets/table.md @@ -3,3 +3,90 @@ sidebar_position: 1 --- # Table + +Tables can be used for both displaying and editing data. + +ToolJet - line charts + +## Displaying Data + +The data object should be an array of objects. Table columns can be added, removed, rearranged from the inspector. `key` property is the accessor key used to get data from a single element of table data object. For example: + +If the table data is: + +``` +[ + { + "review": { + "title": "An app review" + }, + "user": { + "name": "sam", + "email": "sam@example.com" + }, + } +] +``` + +To display email column, the key for the column should be `user.email`. + +## Cell data types + +- String ( Default ) +- Text +- Badge - can be used to display and edit predefined badges such as status of shipment. +- Multiple badges +- Tags - similar to badges but the values are not predefined. +- Dropdown +- Multiselect dropdown + +## Client-side pagination + +Client-side pagination is enabled by default. The number of records per page is 10 by default and can be changed to upto 50. + +## Server-side pagination + +Server-side pagination can be used to run a query whenever the page is changed. Go to events section of the inspector and change the action for `on page changed` event. Number of records per page needs to be handled in your query. If server-side pagination is enabled, `pageIndex` property will be exposed on the table object, this property will have the current page index. `pageIndex` can be used to query the next set of results when page is changed. + +## Search +Client-side search is enabled by default and server-side search can be enabled from the events section of the inspector. Whenever the search text is changed, the `searchText` property of the table component is updated. If server-side search is enabled, `on search` event is fired after the content of `searchText` property is changed. `searchText` can be used to run a specific query to search for the records in your datasource. + +## Event: On row clicked + +This event is triggered when a table row is clicked. `selectedRow` property of the table object will have the table data of the selected row. + +## Actions +Actions are buttons that will be displayed as the last column of the table. The styles of these buttons can be customised and `on click` actions can be configured. when clicked, `selectedRow` property of the table will have the table data of the row. + +## Property: Loading state (Boolean) +Loading state shows a loading skeleton for the table. This property can be used to show a loading status on the table while data is being loaded. `isLoading` property of a query can be used to get the status of a query. + +## Saving data +Enable `editable` property of a column to make the cells editable. If a data type is not selected, `string` is selected as the data type. + +If the data in a cell is changed, `changeSet` property of the table object will have the index of the row and the field that changed. +For example, if the name field of second row of example in the 'Displaying Data' section is changed, `changeSet` will look like this: + +``` +{ + 2: { + "name": "new name" + } +} +``` + +Along with `changeSet`, `dataUpdates` property will also be changed when the value of a cell changes. `dataUpdates` will have the whole data of the changed index from the table data. `dataUpdates` will look like this for our example: + +``` +[{ + "review": { + "title": "An app review" + }, + "user": { + "name": "new name", + "email": "sam@example.com" + }, +}] +``` + +If the data of a cell is changed, "save changes" button will be shown at the bottom of the table. This button when clicked will trigger the `Bulk update query` event. This event can be used to run a query to update the data on your datasource. \ No newline at end of file diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css index 601532f888..b08abb9302 100644 --- a/docs/src/css/custom.css +++ b/docs/src/css/custom.css @@ -154,3 +154,7 @@ body { strong { color: #3c92dc; } + +.alert a { + color: inherit; +} diff --git a/docs/static/img/widgets/chart/bar.png b/docs/static/img/widgets/chart/bar.png new file mode 100644 index 0000000000..7cc51187b7 Binary files /dev/null and b/docs/static/img/widgets/chart/bar.png differ diff --git a/docs/static/img/widgets/chart/line.png b/docs/static/img/widgets/chart/line.png new file mode 100644 index 0000000000..d2e5be680c Binary files /dev/null and b/docs/static/img/widgets/chart/line.png differ diff --git a/docs/static/img/widgets/chart/pie.png b/docs/static/img/widgets/chart/pie.png new file mode 100644 index 0000000000..717976fb3b Binary files /dev/null and b/docs/static/img/widgets/chart/pie.png differ diff --git a/docs/static/img/widgets/table/adding.gif b/docs/static/img/widgets/table/adding.gif new file mode 100644 index 0000000000..a4e3422809 Binary files /dev/null and b/docs/static/img/widgets/table/adding.gif differ diff --git a/frontend/assets/images/icons/app-menu.svg b/frontend/assets/images/icons/app-menu.svg new file mode 100644 index 0000000000..bcf35bcd78 --- /dev/null +++ b/frontend/assets/images/icons/app-menu.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/assets/images/icons/apps.svg b/frontend/assets/images/icons/apps.svg new file mode 100644 index 0000000000..71042a2765 --- /dev/null +++ b/frontend/assets/images/icons/apps.svg @@ -0,0 +1,10 @@ + + + ic_fluent_office_apps_28_regular + Created with Sketch. + + + + + + \ No newline at end of file diff --git a/frontend/assets/images/icons/copy.svg b/frontend/assets/images/icons/copy.svg new file mode 100644 index 0000000000..9d9da36a32 --- /dev/null +++ b/frontend/assets/images/icons/copy.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/assets/images/icons/download.svg b/frontend/assets/images/icons/download.svg new file mode 100644 index 0000000000..3bccdb6e60 --- /dev/null +++ b/frontend/assets/images/icons/download.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/assets/images/icons/edit-source.svg b/frontend/assets/images/icons/edit-source.svg new file mode 100644 index 0000000000..3137142c4c --- /dev/null +++ b/frontend/assets/images/icons/edit-source.svg @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/assets/images/icons/edit.svg b/frontend/assets/images/icons/edit.svg new file mode 100644 index 0000000000..3137142c4c --- /dev/null +++ b/frontend/assets/images/icons/edit.svg @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/assets/images/icons/editor/datasources/airtable.svg b/frontend/assets/images/icons/editor/datasources/airtable.svg new file mode 100644 index 0000000000..e670715f02 --- /dev/null +++ b/frontend/assets/images/icons/editor/datasources/airtable.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + diff --git a/frontend/assets/images/icons/editor/datasources/dynamodb.svg b/frontend/assets/images/icons/editor/datasources/dynamodb.svg new file mode 100644 index 0000000000..b8f0d359e7 --- /dev/null +++ b/frontend/assets/images/icons/editor/datasources/dynamodb.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/assets/images/icons/editor/datasources/elasticsearch.svg b/frontend/assets/images/icons/editor/datasources/elasticsearch.svg new file mode 100644 index 0000000000..cb5b7214c8 --- /dev/null +++ b/frontend/assets/images/icons/editor/datasources/elasticsearch.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/assets/images/icons/editor/datasources/firestore.svg b/frontend/assets/images/icons/editor/datasources/firestore.svg new file mode 100644 index 0000000000..c1d7dbb42f --- /dev/null +++ b/frontend/assets/images/icons/editor/datasources/firestore.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/frontend/assets/images/icons/editor/datasources/googlesheets.svg b/frontend/assets/images/icons/editor/datasources/googlesheets.svg new file mode 100644 index 0000000000..bd5d938c78 --- /dev/null +++ b/frontend/assets/images/icons/editor/datasources/googlesheets.svg @@ -0,0 +1,89 @@ + + + + Sheets-icon + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/assets/images/icons/editor/datasources/mongodb.svg b/frontend/assets/images/icons/editor/datasources/mongodb.svg new file mode 100644 index 0000000000..764ccf588f --- /dev/null +++ b/frontend/assets/images/icons/editor/datasources/mongodb.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/assets/images/icons/editor/datasources/mysql.svg b/frontend/assets/images/icons/editor/datasources/mysql.svg new file mode 100644 index 0000000000..f13b56a5d2 --- /dev/null +++ b/frontend/assets/images/icons/editor/datasources/mysql.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/assets/images/icons/editor/datasources/postgresql.svg b/frontend/assets/images/icons/editor/datasources/postgresql.svg new file mode 100644 index 0000000000..1fc0846bdd --- /dev/null +++ b/frontend/assets/images/icons/editor/datasources/postgresql.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/assets/images/icons/editor/datasources/redis.svg b/frontend/assets/images/icons/editor/datasources/redis.svg new file mode 100644 index 0000000000..30a1498ac2 --- /dev/null +++ b/frontend/assets/images/icons/editor/datasources/redis.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/assets/images/icons/editor/datasources/restapi.svg b/frontend/assets/images/icons/editor/datasources/restapi.svg new file mode 100644 index 0000000000..0975234f8e --- /dev/null +++ b/frontend/assets/images/icons/editor/datasources/restapi.svg @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/assets/images/icons/editor/datasources/slack.svg b/frontend/assets/images/icons/editor/datasources/slack.svg new file mode 100644 index 0000000000..0e925dc209 --- /dev/null +++ b/frontend/assets/images/icons/editor/datasources/slack.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/assets/images/icons/editor/datasources/stripe.svg b/frontend/assets/images/icons/editor/datasources/stripe.svg new file mode 100644 index 0000000000..37b894f9e8 --- /dev/null +++ b/frontend/assets/images/icons/editor/datasources/stripe.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + diff --git a/frontend/assets/images/icons/editor/rearrange.svg b/frontend/assets/images/icons/editor/rearrange.svg new file mode 100644 index 0000000000..9e2ad27958 --- /dev/null +++ b/frontend/assets/images/icons/editor/rearrange.svg @@ -0,0 +1,17 @@ + + + rearrange + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/assets/images/icons/filter.svg b/frontend/assets/images/icons/filter.svg new file mode 100644 index 0000000000..b58c2ccf55 --- /dev/null +++ b/frontend/assets/images/icons/filter.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/assets/images/icons/insert.svg b/frontend/assets/images/icons/insert.svg new file mode 100644 index 0000000000..594ff51e5a --- /dev/null +++ b/frontend/assets/images/icons/insert.svg @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/assets/images/icons/lens.svg b/frontend/assets/images/icons/lens.svg new file mode 100644 index 0000000000..260b0beb0c --- /dev/null +++ b/frontend/assets/images/icons/lens.svg @@ -0,0 +1,9 @@ + + + + + diff --git a/frontend/assets/images/icons/maximize.svg b/frontend/assets/images/icons/maximize.svg new file mode 100644 index 0000000000..9cef2e5450 --- /dev/null +++ b/frontend/assets/images/icons/maximize.svg @@ -0,0 +1,11 @@ + + + + ic_fluent_arrow_maximize_28_regular + Created with Sketch. + + + + + + \ No newline at end of file diff --git a/frontend/assets/images/icons/menu.svg b/frontend/assets/images/icons/menu.svg new file mode 100644 index 0000000000..a9dcdac5e8 --- /dev/null +++ b/frontend/assets/images/icons/menu.svg @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/assets/images/icons/minimize.svg b/frontend/assets/images/icons/minimize.svg new file mode 100644 index 0000000000..ff8d2e51c5 --- /dev/null +++ b/frontend/assets/images/icons/minimize.svg @@ -0,0 +1,11 @@ + + + + ic_fluent_arrow_minimize_28_filled + Created with Sketch. + + + + + + \ No newline at end of file diff --git a/frontend/assets/images/icons/padlock.svg b/frontend/assets/images/icons/padlock.svg new file mode 100644 index 0000000000..1f4b9bf2f8 --- /dev/null +++ b/frontend/assets/images/icons/padlock.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/assets/images/icons/trash.svg b/frontend/assets/images/icons/trash.svg new file mode 100644 index 0000000000..bdc0261174 --- /dev/null +++ b/frontend/assets/images/icons/trash.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/assets/images/icons/users.svg b/frontend/assets/images/icons/users.svg new file mode 100644 index 0000000000..10e85199ba --- /dev/null +++ b/frontend/assets/images/icons/users.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/assets/images/icons/widgets/modal.png b/frontend/assets/images/icons/widgets/modal.png new file mode 100644 index 0000000000..8ed307ba3e Binary files /dev/null and b/frontend/assets/images/icons/widgets/modal.png differ diff --git a/frontend/assets/images/icons/zoom-in.svg b/frontend/assets/images/icons/zoom-in.svg new file mode 100644 index 0000000000..6b79c40cc3 --- /dev/null +++ b/frontend/assets/images/icons/zoom-in.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/assets/images/icons/zoom-out.svg b/frontend/assets/images/icons/zoom-out.svg new file mode 100644 index 0000000000..48ff0ebd75 --- /dev/null +++ b/frontend/assets/images/icons/zoom-out.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 465cadcdfb..5e6c90faf5 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -13710,6 +13710,23 @@ "react-draggable": "^4.0.3" } }, + "react-rnd": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/react-rnd/-/react-rnd-10.3.0.tgz", + "integrity": "sha512-v+0TRPIaRWY25TYv02vLQHYpACbkX+4xKvsyIrUEy4bMpq0bP1oEiaxTorp0Xn72IVv0QZV1vOnZimgTEB/skw==", + "requires": { + "re-resizable": "6.9.0", + "react-draggable": "4.4.3", + "tslib": "2.2.0" + }, + "dependencies": { + "tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" + } + } + }, "react-router": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index bc48caf87d..bd1ad6bb3c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -38,6 +38,7 @@ "react-loading-skeleton": "^2.2.0", "react-plotly.js": "^2.5.1", "react-resizable": "^1.11.1", + "react-rnd": "^10.3.0", "react-router-dom": "^5.0.0", "react-scripts": "3.4.3", "react-select-search": "^3.0.5", @@ -46,6 +47,7 @@ "react-toastify": "^7.0.3", "react-tooltip": "^4.2.18", "rxjs": "^6.3.3", + "tinycolor2": "^1.4.2", "yup": "^0.27.0" }, "devDependencies": { diff --git a/frontend/src/Editor/Box.jsx b/frontend/src/Editor/Box.jsx index 5fb22b77da..7ff034c3e9 100644 --- a/frontend/src/Editor/Box.jsx +++ b/frontend/src/Editor/Box.jsx @@ -56,7 +56,7 @@ export const Box = function Box({ const backgroundColor = yellow ? 'yellow' : ''; let styles = { - cursor: mode === 'edit' ? 'move' : '' + }; if (inCanvas) { @@ -85,7 +85,7 @@ export const Box = function Box({ containerProps={containerProps} > ) : ( -
+
{ + const value = editor.getValue(); + onChange(value); + }} onChange={(editor) => handleChange(editor, onChange, suggestions)} onBeforeChange={(editor, change) => onBeforeChange(editor, change)} options={options} diff --git a/frontend/src/Editor/CodeBuilder/utils.js b/frontend/src/Editor/CodeBuilder/utils.js index 0b100560ef..1d15962af6 100644 --- a/frontend/src/Editor/CodeBuilder/utils.js +++ b/frontend/src/Editor/CodeBuilder/utils.js @@ -75,9 +75,6 @@ export function canShowHint(editor) { export function handleChange(editor, onChange, suggestions) { - const value = editor.getValue(); - onChange(value); - let state = editor.state.matchHighlighter; editor.addOverlay(state.overlay = makeOverlay(state.options.style)); diff --git a/frontend/src/Editor/Components/Button.jsx b/frontend/src/Editor/Components/Button.jsx index 7cb8e0566a..b3d188968e 100644 --- a/frontend/src/Editor/Components/Button.jsx +++ b/frontend/src/Editor/Components/Button.jsx @@ -1,5 +1,6 @@ import React, { useState, useEffect } from 'react'; import { resolveReferences } from '@/_helpers/utils'; +var tinycolor = require("tinycolor2"); export const Button = function Button({ id, width, height, component, onComponentClick, currentState @@ -24,12 +25,13 @@ export const Button = function Button({ backgroundColor, color, width, - height + height, + '--tblr-btn-color-darker': tinycolor(backgroundColor).darken(8).toString() }; return ( +
)} diff --git a/frontend/src/Editor/DataSourceManager/DataSourceTypes.js b/frontend/src/Editor/DataSourceManager/DataSourceTypes.js index fbb35ff4f2..8b1cc2e5db 100644 --- a/frontend/src/Editor/DataSourceManager/DataSourceTypes.js +++ b/frontend/src/Editor/DataSourceManager/DataSourceTypes.js @@ -2,7 +2,6 @@ export const dataBaseSources = [ { name: 'PostgreSQL', kind: 'postgresql', - icon: 'https://www.svgrepo.com/show/303301/postgresql-logo.svg', options: { host: { type: 'string' }, port: { type: 'string' }, @@ -19,7 +18,6 @@ export const dataBaseSources = [ { name: 'MySQL', kind: 'mysql', - icon: 'https://www.svgrepo.com/show/303251/mysql-logo.svg', exposedVariables: { isLoading: {}, data: {}, @@ -36,7 +34,6 @@ export const dataBaseSources = [ { name: 'MongoDB', kind: 'mongodb', - icon: 'https://cdn.worldvectorlogo.com/logos/mongodb-icon-1.svg', exposedVariables: { isLoading: {}, data: {}, @@ -54,7 +51,6 @@ export const dataBaseSources = [ { name: 'Firestore', kind: 'firestore', - icon: 'https://static.invertase.io/assets/firebase/cloud-firestore.svg', exposedVariables: { isLoading: {}, data: [], @@ -67,7 +63,6 @@ export const dataBaseSources = [ { name: 'DynamoDB', kind: 'dynamodb', - icon: 'https://pics.freeicons.io/uploads/icons/png/9820297401540553608-512.png', exposedVariables: { isLoading: {}, data: {}, @@ -82,7 +77,6 @@ export const dataBaseSources = [ { name: 'Elasticsearch', kind: 'elasticsearch', - icon: 'https://cdn.worldvectorlogo.com/logos/elastic-elasticsearch.svg', exposedVariables: { isLoading: {}, data: {}, @@ -98,7 +92,6 @@ export const dataBaseSources = [ { name: 'Redis', kind: 'redis', - icon: 'https://www.svgrepo.com/show/303460/redis-logo.svg', exposedVariables: { isLoading: {}, data: {}, @@ -117,7 +110,6 @@ export const apiSources = [ { name: 'Rest API', kind: 'restapi', - icon: 'https://www.svgrepo.com/show/120283/api.svg', options: { url: { type: 'string' }, auth_type: { type: 'string' }, @@ -143,7 +135,19 @@ export const apiSources = [ { name: 'Stripe', kind: 'stripe', - icon: 'https://upload.wikimedia.org/wikipedia/commons/b/ba/Stripe_Logo%2C_revised_2016.svg', + exposedVariables: { + isLoading: {}, + data: {}, + rawData: {} + }, + options: { + api_key: { type: 'string', encrypted: true } + }, + customTesting: true + }, + { + name: 'Airtable', + kind: 'airtable', exposedVariables: { isLoading: {}, data: {}, @@ -157,7 +161,6 @@ export const apiSources = [ { name: 'Google Sheets', kind: 'googlesheets', - icon: 'https://upload.wikimedia.org/wikipedia/commons/3/30/Google_Sheets_logo_%282014-2020%29.svg', exposedVariables: { isLoading: {}, data: {}, @@ -171,7 +174,6 @@ export const apiSources = [ { name: 'Slack', kind: 'slack', - icon: 'https://www.svgrepo.com/show/303320/slack-new-logo-logo.svg', exposedVariables: { isLoading: {}, data: {}, diff --git a/frontend/src/Editor/DataSourceManager/DefaultOptions.js b/frontend/src/Editor/DataSourceManager/DefaultOptions.js index b228efb675..9f71ff9481 100644 --- a/frontend/src/Editor/DataSourceManager/DefaultOptions.js +++ b/frontend/src/Editor/DataSourceManager/DefaultOptions.js @@ -30,6 +30,7 @@ export const defaultOptions = { }, elasticsearch: { + scheme: { value: 'https' }, host: { value: 'localhost' }, port: { value: 9200 }, username: { value: '' }, @@ -38,6 +39,9 @@ export const defaultOptions = { stripe: { api_key: { value: '' } }, + airtable: { + api_key: { value: '' } + }, firestore: { gcp_key: { value: '' } }, diff --git a/frontend/src/Editor/DataSourceManager/SourceComponents/Airtable.jsx b/frontend/src/Editor/DataSourceManager/SourceComponents/Airtable.jsx new file mode 100644 index 0000000000..a3f565a10a --- /dev/null +++ b/frontend/src/Editor/DataSourceManager/SourceComponents/Airtable.jsx @@ -0,0 +1,42 @@ +import React from 'react'; +import Button from 'react-bootstrap/Button'; + +export const Airtable = ({ + optionchanged, createDataSource, options, isSaving +}) => { + return ( +
+
+
+ + optionchanged('api_key', e.target.value)} + value={options.api_key.value} + /> + + For generating API key, visit:{' '} + + Airtable account page + + +
+
+
+
+
+ +
+
+
+ ); +}; diff --git a/frontend/src/Editor/DataSourceManager/SourceComponents/Dynamodb.jsx b/frontend/src/Editor/DataSourceManager/SourceComponents/Dynamodb.jsx index 76c0eeda90..ee97c3e59c 100644 --- a/frontend/src/Editor/DataSourceManager/SourceComponents/Dynamodb.jsx +++ b/frontend/src/Editor/DataSourceManager/SourceComponents/Dynamodb.jsx @@ -40,7 +40,7 @@ export const Dynamodb = ({ optionchanged, options }) => { diff --git a/frontend/src/Editor/DataSourceManager/SourceComponents/Elasticsearch.jsx b/frontend/src/Editor/DataSourceManager/SourceComponents/Elasticsearch.jsx index 7183c40e29..320a053fd3 100644 --- a/frontend/src/Editor/DataSourceManager/SourceComponents/Elasticsearch.jsx +++ b/frontend/src/Editor/DataSourceManager/SourceComponents/Elasticsearch.jsx @@ -1,4 +1,5 @@ import React from 'react'; +import SelectSearch, { fuzzySearch } from 'react-select-search'; export const Elasticsearch = ({ optionchanged, options @@ -6,7 +7,25 @@ export const Elasticsearch = ({ return (
-
+
+ + { + optionchanged('scheme', value); + }} + filterOptions={fuzzySearch} + placeholder="Select.." + /> +
+
+
+
+ + optionchanged('username', e.target.value)} + value={options.username.value} + /> +
+
+ + optionchanged('password', e.target.value)} + value={options.password.value} + /> +
+
); diff --git a/frontend/src/Editor/DataSourceManager/SourceComponents/Firestore.jsx b/frontend/src/Editor/DataSourceManager/SourceComponents/Firestore.jsx index dadfb90f8f..041cbab097 100644 --- a/frontend/src/Editor/DataSourceManager/SourceComponents/Firestore.jsx +++ b/frontend/src/Editor/DataSourceManager/SourceComponents/Firestore.jsx @@ -8,7 +8,7 @@ export const Firestore = ({ optionchanged, options }) => { diff --git a/frontend/src/Editor/DataSourceManager/SourceComponents/Mongodb.jsx b/frontend/src/Editor/DataSourceManager/SourceComponents/Mongodb.jsx index dd07d20b35..103ec48582 100644 --- a/frontend/src/Editor/DataSourceManager/SourceComponents/Mongodb.jsx +++ b/frontend/src/Editor/DataSourceManager/SourceComponents/Mongodb.jsx @@ -25,7 +25,7 @@ export const Mongodb = ({ optionchanged, options }) => { diff --git a/frontend/src/Editor/DataSourceManager/SourceComponents/Mysql.jsx b/frontend/src/Editor/DataSourceManager/SourceComponents/Mysql.jsx index 6db503d56e..9f1a1310a7 100644 --- a/frontend/src/Editor/DataSourceManager/SourceComponents/Mysql.jsx +++ b/frontend/src/Editor/DataSourceManager/SourceComponents/Mysql.jsx @@ -48,7 +48,7 @@ export const Mysql = ({ diff --git a/frontend/src/Editor/DataSourceManager/SourceComponents/Postgresql.jsx b/frontend/src/Editor/DataSourceManager/SourceComponents/Postgresql.jsx index 477dea6cea..2ea5200d32 100644 --- a/frontend/src/Editor/DataSourceManager/SourceComponents/Postgresql.jsx +++ b/frontend/src/Editor/DataSourceManager/SourceComponents/Postgresql.jsx @@ -46,7 +46,7 @@ export const Postgresql = ({ optionchanged, options }) => { diff --git a/frontend/src/Editor/DataSourceManager/SourceComponents/RestApi.jsx b/frontend/src/Editor/DataSourceManager/SourceComponents/RestApi.jsx index de1e8f4519..f2199119b4 100644 --- a/frontend/src/Editor/DataSourceManager/SourceComponents/RestApi.jsx +++ b/frontend/src/Editor/DataSourceManager/SourceComponents/RestApi.jsx @@ -186,7 +186,7 @@ export const Restapi = ({ diff --git a/frontend/src/Editor/DataSourceManager/SourceComponents/Stripe.jsx b/frontend/src/Editor/DataSourceManager/SourceComponents/Stripe.jsx index 4a48f75c22..6691c40b5d 100644 --- a/frontend/src/Editor/DataSourceManager/SourceComponents/Stripe.jsx +++ b/frontend/src/Editor/DataSourceManager/SourceComponents/Stripe.jsx @@ -11,7 +11,7 @@ export const Stripe = ({ diff --git a/frontend/src/Editor/DataSourceManager/SourceComponents/index.js b/frontend/src/Editor/DataSourceManager/SourceComponents/index.js index 76a5eff409..007afd4ee0 100644 --- a/frontend/src/Editor/DataSourceManager/SourceComponents/index.js +++ b/frontend/src/Editor/DataSourceManager/SourceComponents/index.js @@ -9,6 +9,7 @@ import { Googlesheets } from './Googlesheets'; import { Slack } from './Slack'; import { Mongodb } from './Mongodb'; import { Dynamodb } from './Dynamodb'; +import { Airtable } from './Airtable'; export const SourceComponents = { Elasticsearch, @@ -21,5 +22,6 @@ export const SourceComponents = { Googlesheets, Slack, Mongodb, - Dynamodb + Dynamodb, + Airtable }; diff --git a/frontend/src/Editor/DataSourceManager/TestConnection.jsx b/frontend/src/Editor/DataSourceManager/TestConnection.jsx index 09df9dcbe4..a5e222a522 100644 --- a/frontend/src/Editor/DataSourceManager/TestConnection.jsx +++ b/frontend/src/Editor/DataSourceManager/TestConnection.jsx @@ -31,7 +31,7 @@ export const TestConnection = ({ kind, options }) => { setConnectionStatus('success'); toast.success('Datasource Connection Tested, Successfully!', { hideProgressBar: true, position: 'top-center' }); }, - (error) => { + ({error}) => { setTestingStatus(false); setConnectionStatus('failed'); toast.error(error, { hideProgressBar: true, position: 'top-center' }); diff --git a/frontend/src/Editor/DraggableBox.jsx b/frontend/src/Editor/DraggableBox.jsx index a50a448ad7..28b6582552 100644 --- a/frontend/src/Editor/DraggableBox.jsx +++ b/frontend/src/Editor/DraggableBox.jsx @@ -5,6 +5,7 @@ import { getEmptyImage } from 'react-dnd-html5-backend'; import { Box } from './Box'; import { Resizable } from 're-resizable'; import { ConfigHandle } from './ConfigHandle'; +import { Rnd } from "react-rnd"; const resizerClasses = { topRight: 'top-right', @@ -32,8 +33,8 @@ function getStyles(left, top, isDragging, component) { const transform = `translate3d(${left}px, ${top}px, 0)`; return { position: 'absolute', - transform, - WebkitTransform: transform, + // transform, + // WebkitTransform: transform, zIndex: ['DropDown', 'Datepicker', 'DaterangePicker'].includes(component.component) ? 2 : 1, // IE fallback: hide the real node using CSS when dragging // because IE will ignore our custom "empty image" drag preview. @@ -69,6 +70,7 @@ export const DraggableBox = function DraggableBox({ }) { const [isResizing, setResizing] = useState(false); const [canDrag, setCanDrag] = useState(true); + const [mouseOver, setMouseOver] = useState(false); const [{ isDragging }, drag, preview] = useDrag( () => ({ @@ -115,22 +117,31 @@ export const DraggableBox = function DraggableBox({ return (
{inCanvas ? ( -
+
setMouseOver(true)} + onMouseLeave={() => setMouseOver(false)} + > - setResizing(true)} - handleClasses={resizerClasses} - handleStyles={resizerStyles} - onResizeStop={(e, direction, ref, d) => { + resizeHandleClasses={mouseOver ? resizerClasses : {}} + resizeHandleStyles={resizerStyles} + disableDragging={true} + enableResizing={mode === 'edit'} + onResizeStop={(e, direction, ref, d, position) => { setResizing(false); - onResizeStop(id, width, height, e, direction, ref, d); + onResizeStop(id, e, direction, ref, d, position); }} >
- {mode === 'edit' && + {mode === 'edit' && mouseOver &&
-
+
) : ( -
+
- {dataSource.name} + {dataSource.name} ); @@ -314,7 +314,7 @@ class Editor extends React.Component { role="button" >
- + {dataQuery.name}
@@ -465,7 +465,7 @@ class Editor extends React.Component { role="button" > @@ -480,7 +480,7 @@ class Editor extends React.Component { role="button" > @@ -670,7 +670,7 @@ class Editor extends React.Component {
{/* {} */} - +   Insert @@ -777,9 +777,9 @@ class Editor extends React.Component { dataQueries={dataQueries} componentChanged={this.componentChanged} removeComponent={this.removeComponent} - selectedComponent={selectedComponent} + selectedComponentId={selectedComponent.id} currentState={currentState} - components={appDefinition.components} + allComponents={appDefinition.components} key={selectedComponent.id} > ) : ( diff --git a/frontend/src/Editor/Inspector/Components/Table.jsx b/frontend/src/Editor/Inspector/Components/Table.jsx index 0eb5cfb7d6..2c1daff05d 100644 --- a/frontend/src/Editor/Inspector/Components/Table.jsx +++ b/frontend/src/Editor/Inspector/Components/Table.jsx @@ -54,13 +54,13 @@ class Table extends React.Component { } onActionButtonPropertyChanged = (index, property, value) => { - const actions = this.state.component.component.definition.properties.actions; + const actions = this.props.component.component.definition.properties.actions; actions.value[index][property] = value; this.props.paramUpdated({ name: 'actions' }, 'value', actions.value, 'properties'); }; actionButtonEventUpdated = (event, value, extraData) => { - const actions = this.state.component.component.definition.properties.actions; + const actions = this.props.component.component.definition.properties.actions; const index = extraData.index; let newValues = actions.value; @@ -72,7 +72,7 @@ class Table extends React.Component { }; actionButtonEventOptionUpdated = (event, option, value, extraData) => { - const actions = this.state.component.component.definition.properties.actions; + const actions = this.props.component.component.definition.properties.actions; const index = extraData.index; let newValues = actions.value; @@ -169,17 +169,15 @@ class Table extends React.Component {
)} - {column.columnType === 'string' || column.columnType === 'text' && ( - - )} +