Merge conflicts
commit
fb80d4c597
40
Gemfile.lock
40
Gemfile.lock
|
@ -6,9 +6,9 @@ GIT
|
|||
|
||||
GIT
|
||||
remote: https://github.com/fog/fog-google
|
||||
revision: 069b89ffb6d0a7430e6e49eb24266c8d555ad135
|
||||
revision: ee58e2a4d9502f2a4dc102ca6a4b664656551e3f
|
||||
specs:
|
||||
fog-google (1.2.2)
|
||||
fog-google (1.3.3)
|
||||
fog-core
|
||||
fog-json
|
||||
fog-xml
|
||||
|
@ -65,7 +65,7 @@ GEM
|
|||
arel (8.0.0)
|
||||
bcrypt (3.1.11)
|
||||
builder (3.2.3)
|
||||
bunny (2.9.1)
|
||||
bunny (2.9.2)
|
||||
amq-protocol (~> 2.3.0)
|
||||
capybara (2.18.0)
|
||||
addressable
|
||||
|
@ -76,12 +76,10 @@ GEM
|
|||
xpath (>= 2.0, < 4.0)
|
||||
case_transform (0.2)
|
||||
activesupport
|
||||
childprocess (0.8.0)
|
||||
childprocess (0.9.0)
|
||||
ffi (~> 1.0, >= 1.0.11)
|
||||
choice (0.2.0)
|
||||
climate_control (0.2.0)
|
||||
cocaine (0.5.8)
|
||||
climate_control (>= 0.0.3, < 1.0)
|
||||
codecov (0.1.10)
|
||||
json
|
||||
simplecov
|
||||
|
@ -98,17 +96,17 @@ GEM
|
|||
delayed_job_active_record (4.1.2)
|
||||
activerecord (>= 3.0, < 5.2)
|
||||
delayed_job (>= 3.0, < 5)
|
||||
devise (4.4.1)
|
||||
devise (4.4.3)
|
||||
bcrypt (~> 3.0)
|
||||
orm_adapter (~> 0.1)
|
||||
railties (>= 4.1.0, < 5.2)
|
||||
railties (>= 4.1.0, < 6.0)
|
||||
responders
|
||||
warden (~> 1.2.3)
|
||||
diff-lcs (1.3)
|
||||
docile (1.1.5)
|
||||
erubi (1.7.0)
|
||||
docile (1.3.0)
|
||||
erubi (1.7.1)
|
||||
eventmachine (1.2.5)
|
||||
excon (0.60.0)
|
||||
excon (0.61.0)
|
||||
factory_bot (4.8.2)
|
||||
activesupport (>= 3.0.0)
|
||||
factory_bot_rails (4.8.2)
|
||||
|
@ -121,7 +119,7 @@ GEM
|
|||
ffi (1.9.23)
|
||||
figaro (1.1.1)
|
||||
thor (~> 0.14)
|
||||
fog-core (2.0.0)
|
||||
fog-core (2.1.0)
|
||||
builder
|
||||
excon (~> 0.58)
|
||||
formatador (~> 0.2)
|
||||
|
@ -190,12 +188,12 @@ GEM
|
|||
mini_portile2 (~> 2.3.0)
|
||||
orm_adapter (0.5.0)
|
||||
os (0.9.6)
|
||||
paperclip (5.2.1)
|
||||
paperclip (6.0.0)
|
||||
activemodel (>= 4.2.0)
|
||||
activesupport (>= 4.2.0)
|
||||
cocaine (~> 0.5.5)
|
||||
mime-types
|
||||
mimemagic (~> 0.3.0)
|
||||
terrapin (~> 0.6.0)
|
||||
pg (0.21.0)
|
||||
polymorphic_constraints (1.0.0)
|
||||
rails
|
||||
|
@ -206,10 +204,10 @@ GEM
|
|||
pry (>= 0.10.4)
|
||||
public_suffix (3.0.2)
|
||||
rack (2.0.4)
|
||||
rack-attack (5.0.1)
|
||||
rack-attack (5.1.0)
|
||||
rack
|
||||
rack-cors (1.0.2)
|
||||
rack-test (0.8.2)
|
||||
rack-test (0.8.3)
|
||||
rack (>= 1.0, < 3)
|
||||
rails (5.1.5)
|
||||
actioncable (= 5.1.5)
|
||||
|
@ -249,7 +247,7 @@ GEM
|
|||
declarative (< 0.1.0)
|
||||
declarative-option (< 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
request_store (1.4.0)
|
||||
request_store (1.4.1)
|
||||
rack (>= 1.4)
|
||||
responders (2.4.0)
|
||||
actionpack (>= 4.2.0, < 5.3)
|
||||
|
@ -282,7 +280,7 @@ GEM
|
|||
rubyzip (1.2.1)
|
||||
secure_headers (5.0.5)
|
||||
useragent (>= 0.15.0)
|
||||
selenium-webdriver (3.9.0)
|
||||
selenium-webdriver (3.11.0)
|
||||
childprocess (~> 0.5)
|
||||
rubyzip (~> 1.2)
|
||||
signet (0.8.1)
|
||||
|
@ -290,8 +288,8 @@ GEM
|
|||
faraday (~> 0.9)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
multi_json (~> 1.10)
|
||||
simplecov (0.15.1)
|
||||
docile (~> 1.1.0)
|
||||
simplecov (0.16.1)
|
||||
docile (~> 1.1)
|
||||
json (>= 1.8, < 3)
|
||||
simplecov-html (~> 0.10.0)
|
||||
simplecov-html (0.10.2)
|
||||
|
@ -304,6 +302,8 @@ GEM
|
|||
actionpack (>= 4.0)
|
||||
activesupport (>= 4.0)
|
||||
sprockets (>= 3.0.0)
|
||||
terrapin (0.6.0)
|
||||
climate_control (>= 0.0.3, < 1.0)
|
||||
thin (1.7.2)
|
||||
daemons (~> 1.0, >= 1.0.9)
|
||||
eventmachine (~> 1.0, >= 1.0.4)
|
||||
|
|
12
README.md
12
README.md
|
@ -1,11 +1,7 @@
|
|||
[![codebeat badge](https://codebeat.co/badges/7b023dc5-6509-42af-ad6e-ec0b8262ef13)](https://codebeat.co/projects/github-com-rickcarlino-farmbot-web-app-master)
|
||||
|
||||
|
||||
[![Coverage Status](https://coveralls.io/repos/github/FarmBot/Farmbot-Web-App/badge.svg)](https://coveralls.io/github/FarmBot/Farmbot-Web-App)(Frontend)
|
||||
|
||||
|
||||
[![codecov](https://codecov.io/gh/FarmBot/Farmbot-Web-App/branch/master/graph/badge.svg)](https://codecov.io/gh/FarmBot/Farmbot-Web-App)(API)
|
||||
|
||||
# FarmBot Web App
|
||||
[![codebeat badge](https://codebeat.co/badges/7f81859b-67fe-4bdb-b56f-050bfed35e9c)](https://codebeat.co/projects/github-com-farmbot-farmbot-web-app-staging)
|
||||
[![Coverage Status](https://coveralls.io/repos/github/FarmBot/Farmbot-Web-App/badge.svg)](https://coveralls.io/github/FarmBot/Farmbot-Web-App)
|
||||
[![codecov](https://codecov.io/gh/FarmBot/Farmbot-Web-App/branch/master/graph/badge.svg)](https://codecov.io/gh/FarmBot/Farmbot-Web-App)
|
||||
[![Maintainability](https://api.codeclimate.com/v1/badges/74091163d8a02bb8988f/maintainability)](https://codeclimate.com/github/FarmBot/Farmbot-Web-App/maintainability)
|
||||
|
||||
|
||||
|
|
|
@ -15,7 +15,6 @@ module Api
|
|||
skip_before_action :verify_authenticity_token
|
||||
after_action :skip_set_cookies_header
|
||||
|
||||
rescue_from(User::BadSub) { sorry "Please log out and try again.", 422 }
|
||||
rescue_from(User::AlreadyVerified) { sorry "Already verified.", 409 }
|
||||
|
||||
rescue_from(JWT::VerificationError) { |e| auth_err }
|
||||
|
|
|
@ -29,8 +29,6 @@ module CeleryScript
|
|||
flat_ir
|
||||
.each do |node|
|
||||
# Step 1- instantiate records.
|
||||
# TODO: Switch create!() to new() once things are atleast working
|
||||
# - RC
|
||||
node[I] = PrimaryNode.create!(kind: node[K],
|
||||
sequence: sequence,
|
||||
comment: node[C] || nil)
|
||||
|
|
|
@ -4,9 +4,7 @@ class Image < ApplicationRecord
|
|||
belongs_to :device
|
||||
validates :device, presence: true
|
||||
serialize :meta
|
||||
# TODO: EASY: Why can't I set this meta fields default value to '{}'?
|
||||
# Tests are failing when I do. PRs appreciated.
|
||||
# validates :meta, presence: true
|
||||
|
||||
# http://stackoverflow.com/a/5127684/1064917
|
||||
after_initialize :set_defaults
|
||||
|
||||
|
|
|
@ -17,13 +17,13 @@ class Regimen < ApplicationRecord
|
|||
validates :device, presence: true
|
||||
|
||||
# PROBLEM:
|
||||
# * data_update sends MQTT messages when models update.
|
||||
# * sync messages send MQTT packets when models update in a background job.
|
||||
# * regimen_items are a "nested resource". The user does not know they exist
|
||||
# outside of a regimen
|
||||
# * We still need to be notified of updates to `regimen_item`s.
|
||||
#
|
||||
# SOLUTION:
|
||||
# * _always_ send `data_update` for Regimens, even though its kind of
|
||||
# * _always_ send update messages for Regimens, even though its kind of
|
||||
# wasteful.
|
||||
def notable_changes?
|
||||
true
|
||||
|
|
|
@ -62,14 +62,6 @@ class Sequence < ApplicationRecord
|
|||
Sequence.order("RANDOM()").first
|
||||
end
|
||||
|
||||
# def traverse(&blk)
|
||||
# hash = as_json
|
||||
# .tap { |x| x[:kind] = "sequence" }
|
||||
# .deep_symbolize_keys
|
||||
# .slice(:kind, :args, :body)
|
||||
# CeleryScript::JSONClimber.climb(hash, &blk)
|
||||
# end
|
||||
|
||||
def delete_nodes_too
|
||||
Sequence.transaction do
|
||||
PrimaryNode.where(sequence_id: self.id).destroy_all
|
||||
|
|
|
@ -38,22 +38,4 @@ class User < ApplicationRecord
|
|||
def verified?
|
||||
SKIP_EMAIL_VALIDATION ? true : !!confirmed_at
|
||||
end
|
||||
|
||||
BAD_SUB = "SUB was neither string nor number"
|
||||
class BadSub < StandardError; end # Safe to remove December '17 -RC
|
||||
def self.find_by_email_or_id(sub) # Safe to remove December '17 -RC
|
||||
case sub
|
||||
when Integer then User.find(sub)
|
||||
# HISTORICAL CONTEXT: We once used emails as a `sub` field. At the time,
|
||||
# it seemed nice because it was human readable. The problem was that
|
||||
# emails are mutable. Under this scheme, changing your email address
|
||||
# would invalidate your JWT. Switching it to user_id (that does not
|
||||
# change) gets around this issue. We still need to support emails in
|
||||
# JWTs, atleast for another month or so because it would invalidate
|
||||
# existing tokens otherwise.
|
||||
# TODO: Only use user_id (not email) for validation after 25 OCT 17 - RC
|
||||
when String then User.find_by!(email: sub)
|
||||
else raise BadSub, BAD_SUB
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,10 +8,10 @@ module Auth
|
|||
token = SessionToken.decode!(just_the_token)
|
||||
claims = token.unencoded
|
||||
RequestStore.store[:jwt] = claims.deep_symbolize_keys
|
||||
u = User.includes(:device).find_by_email_or_id(claims["sub"])
|
||||
u = User.includes(:device).find(claims["sub"])
|
||||
Device.current = u.device
|
||||
u
|
||||
rescue JWT::DecodeError, ActiveRecord::RecordNotFound, User::BadSub
|
||||
rescue JWT::DecodeError, ActiveRecord::RecordNotFound
|
||||
add_error :jwt, :decode_error, Auth::ReloadToken::BAD_SUB
|
||||
end
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ module Auth
|
|||
end
|
||||
|
||||
def validate
|
||||
@user = User.find_by_email_or_id(claims["sub"])
|
||||
@user = User.find(claims["sub"])
|
||||
end
|
||||
|
||||
def execute
|
||||
|
|
|
@ -1,77 +0,0 @@
|
|||
module FarmEvents
|
||||
# Used to calculate next 60ish occurrences or so of a FarmEvent.
|
||||
class GenerateCalendar < Mutations::Command
|
||||
GRACE_PERIOD = 5.minutes
|
||||
NEVER = FarmEvent::NEVER.to_s
|
||||
TIME = { "minutely" => 60,
|
||||
"hourly" => 60 * 60,
|
||||
"daily" => 60 * 60 * 24,
|
||||
"weekly" => 60 * 60 * 24 * 7,
|
||||
"monthly" => 60 * 60 * 24 * 30, # Not perfect...
|
||||
"yearly" => 60 * 60 * 24 * 365 }
|
||||
UNIT_TRANSLATION = { "minutely" => :minutes,
|
||||
"hourly" => :hours,
|
||||
"daily" => :days,
|
||||
"weekly" => :weeks,
|
||||
"monthly" => :months,
|
||||
"yearly" => :years }
|
||||
required do
|
||||
integer :repeat
|
||||
string :time_unit, in: FarmEvent::UNITS_OF_TIME
|
||||
time :origin
|
||||
time :lower_limit
|
||||
end
|
||||
|
||||
optional do
|
||||
time :upper_limit
|
||||
end
|
||||
|
||||
def execute
|
||||
# Does the input have a valid repeat?
|
||||
# Is it in the future?
|
||||
# Then generate a calendar.
|
||||
# Otherwise, return a "partial calendar" that is either empty or (in the
|
||||
# case of one-off events) has only one date in it (origin).
|
||||
(is_repeating ? full_calendar : partial_calendar)
|
||||
end
|
||||
|
||||
def full_calendar
|
||||
interval_sec = TIME[time_unit] * repeat
|
||||
upper = compute_endtime
|
||||
# Current time, plus a 5 minute grace period.
|
||||
now = Time.now - GRACE_PERIOD
|
||||
# How many items must we skip to get to the first occurence?
|
||||
skip_intervals = ((lower_limit - origin) / interval_sec).ceil
|
||||
# At what time does the first event occur?
|
||||
first_item = origin + (skip_intervals * interval_sec).seconds
|
||||
list = [first_item]
|
||||
60.times do
|
||||
item = list.last + interval_sec.seconds
|
||||
list.push(item) unless (item >= upper) || (item <= now)
|
||||
end
|
||||
|
||||
return list
|
||||
end
|
||||
|
||||
def partial_calendar
|
||||
in_future? ? [origin] : []
|
||||
end
|
||||
|
||||
def the_unit
|
||||
UNIT_TRANSLATION[time_unit]
|
||||
end
|
||||
|
||||
def is_repeating
|
||||
(the_unit != NEVER) && the_unit && repeat.send(the_unit)
|
||||
end
|
||||
|
||||
def in_future?
|
||||
origin > (Time.now - GRACE_PERIOD)
|
||||
end
|
||||
|
||||
def compute_endtime
|
||||
next_year = (Time.now + 1.year)
|
||||
(upper_limit && (upper_limit < next_year)) ? upper_limit : next_year
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,11 +1,4 @@
|
|||
class DeviceSerializer < ActiveModel::Serializer
|
||||
attributes :id, :name, :timezone, :last_saw_api, :last_saw_mq, :last_seen,
|
||||
:tz_offset_hrs, :fbos_version
|
||||
|
||||
def last_seen
|
||||
# TODO: Remove this by December 2017.
|
||||
# This is a legacy attribute that needs to go away, but will cause
|
||||
# crashes on legacy versions of FBOS.
|
||||
object.last_saw_api
|
||||
end
|
||||
attributes :id, :name, :timezone, :last_saw_api, :last_saw_mq, :tz_offset_hrs,
|
||||
:fbos_version
|
||||
end
|
||||
|
|
|
@ -1,29 +1,5 @@
|
|||
class FarmEventSerializer < ActiveModel::Serializer
|
||||
class BadExe < StandardError; end
|
||||
attributes :id, :start_time, :end_time, :repeat, :time_unit, :executable_id,
|
||||
:executable_type, :calendar
|
||||
|
||||
BAD_EXE = "Dont know how to calendarize %s"
|
||||
|
||||
def calendar
|
||||
case object.executable
|
||||
when Sequence then sequence_calendar
|
||||
when Regimen then []
|
||||
else
|
||||
throw BadExe.new(BAD_EXE % object.executable.class)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def sequence_calendar
|
||||
FarmEvents::GenerateCalendar
|
||||
.run!(origin: object.start_time,
|
||||
lower_limit: Time.now,
|
||||
upper_limit: object.end_time,
|
||||
repeat: object.repeat,
|
||||
time_unit: object.time_unit)
|
||||
.map(&:utc)
|
||||
.map(&:as_json)
|
||||
end
|
||||
:executable_type
|
||||
end
|
||||
|
|
35
package.json
35
package.json
|
@ -24,10 +24,10 @@
|
|||
"author": "farmbot.io",
|
||||
"license": "MIT",
|
||||
"optionalDependencies": {
|
||||
"webpack-dev-server": "^2.9.3"
|
||||
"webpack-dev-server": "^3.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blueprintjs/core": "1.35.5",
|
||||
"@blueprintjs/core": "1.35.7",
|
||||
"@blueprintjs/datetime": "1.25.3",
|
||||
"@blueprintjs/labs": "^0.4.0",
|
||||
"@types/enzyme": "^3.1.9",
|
||||
|
@ -35,10 +35,10 @@
|
|||
"@types/history": "^4.6.1",
|
||||
"@types/i18next": "^8.4.2",
|
||||
"@types/jest": "^21.1.4",
|
||||
"@types/lodash": "^4.14.78",
|
||||
"@types/lodash": "^4.14.105",
|
||||
"@types/markdown-it": "^0.0.4",
|
||||
"@types/moxios": "^0.4.5",
|
||||
"@types/node": "^9.4.6",
|
||||
"@types/node": "^9.4.7",
|
||||
"@types/react": "16.0.34",
|
||||
"@types/react-color": "^2.13.4",
|
||||
"@types/react-dom": "16.0.3",
|
||||
|
@ -49,22 +49,22 @@
|
|||
"boxed_value": "^1.0.0",
|
||||
"browser-speech": "1.1.1",
|
||||
"coveralls": "^3.0.0",
|
||||
"css-loader": "^0.28.7",
|
||||
"css-loader": "^0.28.11",
|
||||
"enzyme": "^3.1.0",
|
||||
"enzyme-adapter-react-16": "^1.1.0",
|
||||
"extract-text-webpack-plugin": "^3.0.1",
|
||||
"farmbot": "5.4.0",
|
||||
"farmbot-toastr": "^1.0.3",
|
||||
"fastclick": "^1.0.6",
|
||||
"file-loader": "^1.1.5",
|
||||
"i18next": "^10.0.3",
|
||||
"imports-loader": "^0.7.0",
|
||||
"file-loader": "^1.1.11",
|
||||
"i18next": "^10.5.1",
|
||||
"imports-loader": "^0.8.0",
|
||||
"jest": "^21.2.1",
|
||||
"json-loader": "^0.5.7",
|
||||
"lodash": "^4.17.4",
|
||||
"markdown-it": "^8.4.0",
|
||||
"markdown-it-emoji": "^1.4.0",
|
||||
"moment": "^2.20.1",
|
||||
"moment": "^2.21.0",
|
||||
"moxios": "^0.4.0",
|
||||
"node-sass": "^4.5.3",
|
||||
"optimize-css-assets-webpack-plugin": "^3.2.0",
|
||||
|
@ -72,7 +72,7 @@
|
|||
"react": "16.2.0",
|
||||
"react-addons-css-transition-group": "^15.6.2",
|
||||
"react-addons-test-utils": "^15.6.2",
|
||||
"react-color": "^2.13.8",
|
||||
"react-color": "^2.14.0",
|
||||
"react-dom": "16.2.0",
|
||||
"react-redux": "^5.0.6",
|
||||
"react-router": "3.2",
|
||||
|
@ -80,17 +80,16 @@
|
|||
"redux": "^3.7.2",
|
||||
"redux-immutable-state-invariant": "^2.1.0",
|
||||
"redux-thunk": "^2.0.1",
|
||||
"sass-loader": "^6.0.6",
|
||||
"sass-loader": "^6.0.7",
|
||||
"stats-webpack-plugin": "^0.6.1",
|
||||
"style-loader": "^0.20.2",
|
||||
"ts-jest": "^22.0.4",
|
||||
"style-loader": "^0.20.3",
|
||||
"ts-jest": "^22.4.2",
|
||||
"ts-lint": "^4.5.1",
|
||||
"ts-loader": "^3.0.5",
|
||||
"ts-loader": "^4.1.0",
|
||||
"tslint": "^5.8.0",
|
||||
"typescript": "2.7.2",
|
||||
"url-loader": "^0.6.2",
|
||||
"webpack": "^3.8.1",
|
||||
"webpack-cli": "^2.0.12",
|
||||
"url-loader": "^1.0.1",
|
||||
"webpack": "^4.1.1",
|
||||
"webpack-uglify-js-plugin": "^1.1.9",
|
||||
"weinre": "^2.0.0-pre-I0Z7U9OV",
|
||||
"which": "^1.3.0",
|
||||
|
@ -98,7 +97,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"jscpd": "^0.6.15",
|
||||
"webpack-notifier": "^1.5.0"
|
||||
"webpack-notifier": "^1.6.0"
|
||||
},
|
||||
"jest": {
|
||||
"globals": {
|
||||
|
|
|
@ -37,7 +37,7 @@ describe DashboardController do
|
|||
expect(user.confirmed_at).to eq(nil)
|
||||
get :verify, params: params
|
||||
user.reload
|
||||
expect(user.confirmation_token).to be # TODO: Hmm..
|
||||
expect(user.confirmation_token).to be
|
||||
expect(user.confirmed_at).to be
|
||||
expect(user.confirmed_at - Time.now).to be < 3
|
||||
end
|
||||
|
|
|
@ -22,16 +22,4 @@ describe Auth::FromJWT do
|
|||
result = Auth::FromJWT.run!(jwt: token)
|
||||
expect(result).to eq(user)
|
||||
end
|
||||
|
||||
it "allows emails as a `sub` field, but only until 25 OCT 17" do
|
||||
t = fake[user.email]
|
||||
result = Auth::FromJWT.run!(jwt: t)
|
||||
expect(result).to eq(user)
|
||||
end
|
||||
|
||||
it "crashes when sub is neither string nor Integer" do
|
||||
expect {
|
||||
Auth::FromJWT.run!(jwt: fake[1.23])
|
||||
}.to raise_error(Auth::ReloadToken::BAD_SUB)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,121 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe FarmEvents::GenerateCalendar do
|
||||
it 'Builds a list of dates' do
|
||||
start = Time.now + 1.minute
|
||||
params = { origin: start,
|
||||
lower_limit: start,
|
||||
upper_limit: start + 1.hours,
|
||||
repeat: 5,
|
||||
time_unit: "minutely" }
|
||||
calendar = FarmEvents::GenerateCalendar.run!(params)
|
||||
expect(calendar.first).to eq(params[:origin])
|
||||
calendar.map { |date| expect(date).to be >= params[:origin] }
|
||||
calendar.map { |date| expect(date).to be <= params[:upper_limit] }
|
||||
expect(calendar.length).to eq(12)
|
||||
end
|
||||
|
||||
it 'hit a bug in production' do
|
||||
start = Time.now + 1.minute
|
||||
finish = start + 5.days
|
||||
params = { origin: start,
|
||||
lower_limit: start,
|
||||
upper_limit: finish,
|
||||
repeat: 1,
|
||||
time_unit: "daily" }
|
||||
calendar = FarmEvents::GenerateCalendar.run!(params)
|
||||
expect(calendar.first.day).to eq(start.day)
|
||||
expect(calendar.length).to be > 4
|
||||
expect(calendar.length).to be < 7
|
||||
end
|
||||
|
||||
it 'has a known calendar bug' do
|
||||
tomorrow = (Time.now + 1.day).midnight
|
||||
ten_am = tomorrow + 10.hours
|
||||
calendar = FarmEvents::GenerateCalendar.run!("origin" => tomorrow,
|
||||
"lower_limit" => tomorrow,
|
||||
"upper_limit" => ten_am,
|
||||
"repeat" => 2,
|
||||
"time_unit" => "hourly")
|
||||
expect(calendar.length).to be > 3
|
||||
expect(calendar.length).to be < 7
|
||||
end
|
||||
|
||||
it 'hit more bugs' do
|
||||
tomorrow = Time.now + 1.day
|
||||
calendar = FarmEvents::GenerateCalendar.run!("origin" => tomorrow,
|
||||
"lower_limit" => tomorrow,
|
||||
"upper_limit" => tomorrow + 5.minutes,
|
||||
"repeat" => 1,
|
||||
"time_unit" => "minutely")
|
||||
expect(calendar.length).to be > 3
|
||||
expect(calendar.length).to be < 7
|
||||
end
|
||||
|
||||
it 'schedules one-off events: origin < lower_limit outside of grace period' do
|
||||
params = { origin: Time.now - 6.minutes,
|
||||
lower_limit: Time.now,
|
||||
upper_limit: nil,
|
||||
repeat: 1,
|
||||
time_unit: FarmEvent::NEVER }
|
||||
calendar = FarmEvents::GenerateCalendar.run!(params)
|
||||
expect(calendar.length).to eq(0)
|
||||
end
|
||||
|
||||
it 'schedules one-off events: origin < lower_limit within grace period' do
|
||||
params = { origin: Time.now - 2.minutes,
|
||||
lower_limit: Time.now,
|
||||
upper_limit: nil,
|
||||
repeat: 1,
|
||||
time_unit: FarmEvent::NEVER }
|
||||
calendar = FarmEvents::GenerateCalendar.run!(params)
|
||||
expect(calendar.length).to eq(1)
|
||||
end
|
||||
|
||||
it 'schedules one-off events: origin = lower_limit' do
|
||||
tomorrow = Time.now + 1.day
|
||||
params = { origin: tomorrow,
|
||||
lower_limit: tomorrow,
|
||||
upper_limit: nil,
|
||||
repeat: 1,
|
||||
time_unit: FarmEvent::NEVER }
|
||||
calendar = FarmEvents::GenerateCalendar.run!(params)
|
||||
expect(calendar.length).to eq(1)
|
||||
expect(calendar.first).to eq(params[:origin])
|
||||
end
|
||||
|
||||
it 'schedules one-off events: origin > lower_limit' do
|
||||
params = { origin: Time.now + 1.day,
|
||||
lower_limit: Time.now - 1.day,
|
||||
upper_limit: nil,
|
||||
repeat: 1,
|
||||
time_unit: FarmEvent::NEVER }
|
||||
calendar = FarmEvents::GenerateCalendar.run!(params)
|
||||
expect(calendar.length).to eq(1)
|
||||
expect(calendar.first).to eq(params[:origin])
|
||||
end
|
||||
|
||||
idea = ->(start, interval_sec, lower, upper = (Time.now + 1.year)) {
|
||||
# How many items must we skip to get to the first occurence?
|
||||
skip_intervals = ((lower - start) / interval_sec).ceil
|
||||
# At what time does the first event occur?
|
||||
first_item = start + (skip_intervals * interval_sec).seconds
|
||||
list = [first_item]
|
||||
60.times do
|
||||
item = list.last + interval_sec.seconds
|
||||
list.push(item) unless item > upper
|
||||
end
|
||||
return list
|
||||
}
|
||||
|
||||
it 'trys new idea' do
|
||||
monday = (Time.now - 14.days).monday.midnight + 8.hours # 8am Monday
|
||||
tuesday = monday + 19.hours # 3am Tuesday
|
||||
thursday = (monday + 3.days) + 10.hours # 18pm Thursday
|
||||
interval = 4 * FarmEvents::GenerateCalendar::TIME["hourly"]
|
||||
result1 = idea[monday, interval, tuesday, thursday]
|
||||
expect(result1[0].tuesday?).to be(true)
|
||||
expect(result1[0].hour).to be(4)
|
||||
expect(result1.length).to be(16)
|
||||
end
|
||||
end
|
|
@ -9,11 +9,4 @@ describe FarmEventSerializer do
|
|||
time_offset: 7000)
|
||||
fe
|
||||
end
|
||||
|
||||
it "does not render `nil` and friends" do
|
||||
farm_event.executable = nil
|
||||
expect{
|
||||
FarmEventSerializer.new(farm_event).as_json
|
||||
}.to raise_error(UncaughtThrowError)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,14 @@
|
|||
import { template, templateSettings } from "lodash";
|
||||
import { Dictionary } from "farmbot";
|
||||
|
||||
templateSettings.interpolate = /{{([\s\S]+?)}}/g;
|
||||
|
||||
const mockTemplate = template;
|
||||
|
||||
jest.mock("i18next", () => ({
|
||||
t: (i: string) => i,
|
||||
t: (i: string, translation: Dictionary<string> = {}): string => {
|
||||
const precompiledTemplate = mockTemplate(i);
|
||||
return precompiledTemplate(translation);
|
||||
},
|
||||
init: jest.fn()
|
||||
}));
|
||||
|
|
|
@ -14,18 +14,17 @@ describe("<HomingRow />", () => {
|
|||
beforeEach(function () {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
// TODO: fix this test
|
||||
// Code being run is {t("HOME {{axis}}", { axis })}
|
||||
// Result string is "HOME {{axis}}HOME {{axis}}HOME {{axis}}"
|
||||
xit("renders three buttons", () => {
|
||||
|
||||
it("renders three buttons", () => {
|
||||
const wrapper = mount(<HomingRow
|
||||
hardware={bot.hardware.mcu_params}
|
||||
botDisconnected={false} />);
|
||||
const txt = wrapper.text();
|
||||
const txt = wrapper.text().toUpperCase();
|
||||
["X", "Y", "Z"].map(function (axis) {
|
||||
expect(txt).toContain(`HOME ${axis}`);
|
||||
});
|
||||
});
|
||||
|
||||
it("calls device", () => {
|
||||
const result = mount(<HomingRow
|
||||
hardware={bot.hardware.mcu_params}
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import * as React from "react";
|
||||
import { JSXChildren } from "../../util";
|
||||
|
||||
interface Props {
|
||||
onClick: Function;
|
||||
disabled: boolean;
|
||||
children?: JSXChildren;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function LockableButton({ onClick, disabled, children }: Props) {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import * as React from "react";
|
||||
import { JSXChildren } from "../util";
|
||||
import { NetworkState } from "../connectivity/interfaces";
|
||||
import { SyncStatus } from "farmbot";
|
||||
|
||||
|
@ -9,7 +8,7 @@ export interface MBOProps {
|
|||
syncStatus: SyncStatus | undefined;
|
||||
lockOpen?: boolean;
|
||||
hideBanner?: boolean;
|
||||
children?: JSXChildren;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function isBotUp(status: SyncStatus | undefined) {
|
||||
|
|
|
@ -171,6 +171,7 @@ export let botReducer = generateReducer<BotState>(initialState(), afterEach)
|
|||
|
||||
versionOK(informational_settings.controller_version,
|
||||
EXPECTED_MAJOR, EXPECTED_MINOR);
|
||||
window.Rollbar && window.Rollbar.configure({ payload: { fbos: informational_settings.controller_version } });
|
||||
state.hardware.informational_settings.sync_status = nextSyncStatus;
|
||||
return state;
|
||||
})
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { SequenceBodyItem as Step } from "farmbot";
|
||||
import { JSXChildren } from "../util";
|
||||
|
||||
/** An entry in the data transfer table. Used to transfer data from a "draggable"
|
||||
* to a "dropable". For type safety, this is a "tagged union". See Typescript
|
||||
|
@ -50,6 +49,6 @@ export interface StepDraggerProps {
|
|||
dispatch: Function;
|
||||
step: Step;
|
||||
intent: DataXferIntent;
|
||||
children?: JSXChildren;
|
||||
children?: React.ReactNode;
|
||||
draggerId: number;
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import { connect } from "react-redux";
|
|||
import { mapStateToPropsAddEdit, } from "./map_state_to_props_add_edit";
|
||||
import { init, destroy } from "../../api/crud";
|
||||
import { EditFEForm } from "./edit_fe_form";
|
||||
import { betterCompact, JSXChildren, catchErrors } from "../../util";
|
||||
import { betterCompact, catchErrors } from "../../util";
|
||||
import { entries } from "../../resources/util";
|
||||
import { Link } from "react-router";
|
||||
import {
|
||||
|
@ -86,7 +86,7 @@ export class AddFarmEvent
|
|||
return <p>{t("Loading")}...</p>;
|
||||
}
|
||||
|
||||
placeholderTemplate(children: JSXChildren) {
|
||||
placeholderTemplate(children: React.ReactChild | React.ReactChild[]) {
|
||||
return <div className="panel-container magenta-panel add-farm-event-panel">
|
||||
<div className="panel-header magenta-panel">
|
||||
<p className="panel-title"> <BackArrow /> {t("No Executables")} </p>
|
||||
|
|
|
@ -190,10 +190,9 @@ export class EditFEForm extends React.Component<EditFEProps, State> {
|
|||
const frmEvnt = this.props.farmEvent;
|
||||
const nextRun = _.first(scheduleForFarmEvent(frmEvnt.body).items);
|
||||
if (nextRun) {
|
||||
// TODO: Internationalizing this will be a challenge.
|
||||
success(`This Farm Event will run ${nextRun.fromNow()}, but
|
||||
success(t(`This Farm Event will run {{timeFromNow}}, but
|
||||
you must first SYNC YOUR DEVICE. If you do not sync, the event will
|
||||
not run.`.replace(/\s+/g, " "));
|
||||
not run.`.replace(/\s+/g, " "), { timeFromNow: nextRun.fromNow() }));
|
||||
this.props.dispatch(maybeWarnAboutMissedTasks(frmEvnt, function () {
|
||||
alert(t(Content.REGIMEN_TODAY_SKIPPED_ITEM_RISK));
|
||||
}));
|
||||
|
|
|
@ -20,9 +20,8 @@ import { SelectionBoxData } from "./map/selection_box";
|
|||
import { BooleanConfigKey } from "../config_storage/web_app_configs";
|
||||
import { GetWebAppConfigValue } from "../config_storage/actions";
|
||||
|
||||
/** TODO: Use Enums */
|
||||
export type BotOriginQuadrant = 1 | 2 | 3 | 4;
|
||||
export type ZoomLevelPayl = 0.1 | -0.1;
|
||||
export enum BotOriginQuadrant { ONE = 1, TWO = 2, THREE = 3, FOUR = 4 }
|
||||
export enum ZoomLevelPayl { POSITIVE = 0.1, NEGATIVE = -0.1 }
|
||||
|
||||
type Mystery = BotOriginQuadrant | number | undefined;
|
||||
export function isBotOriginQuadrant(mystery: Mystery):
|
||||
|
|
|
@ -9,12 +9,12 @@ import { findBySlug } from "../search_selectors";
|
|||
import { Everything } from "../../interfaces";
|
||||
import { OpenFarm } from "../openfarm";
|
||||
import { OFSearch } from "../util";
|
||||
import { catchErrors, JSXChildren } from "../../util";
|
||||
import { catchErrors } from "../../util";
|
||||
import { unselectPlant } from "../actions";
|
||||
|
||||
interface InforFieldProps {
|
||||
title: string;
|
||||
children?: JSXChildren;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
function InfoField(props: InforFieldProps) {
|
||||
|
@ -57,7 +57,8 @@ export class CropInfo extends React.Component<CropInfoProps, {}> {
|
|||
icon ? img.src = DATA_URI + icon : DEFAULT_ICON;
|
||||
|
||||
// TODO: Setting these doesn't work by default, needs a fix
|
||||
// https://www.w3.org/TR/2011/WD-html5-20110405/dnd.html#dom-datatransfer-setdragimage
|
||||
// https://www.w3.org/TR/2011/WD-html5-20110405
|
||||
// /dnd.html#dom-datatransfer-setdragimage
|
||||
img.height = 50;
|
||||
img.width = 50;
|
||||
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { DataChangeType, Dictionary, toPairs, rpcRequest, Pair } from "farmbot/dist";
|
||||
import { getDevice } from "./device";
|
||||
import { DataChangeType, Dictionary } from "farmbot/dist";
|
||||
import { box } from "boxed_value";
|
||||
import * as _ from "lodash";
|
||||
import { ResourceName } from "./resources/tagged_resources";
|
||||
|
||||
export let METHOD_MAP: Dictionary<DataChangeType> = {
|
||||
"post": "add",
|
||||
|
@ -12,50 +10,6 @@ export let METHOD_MAP: Dictionary<DataChangeType> = {
|
|||
};
|
||||
export let METHODS = ["post", "put", "patch", "delete"];
|
||||
|
||||
/** Temporary stub until auto_sync rollout. TODO: Remove */
|
||||
const RESOURNCE_NAME_IN_URL = [
|
||||
"device",
|
||||
"farm_events",
|
||||
"logs",
|
||||
"peripherals",
|
||||
"plants",
|
||||
"points",
|
||||
"regimens",
|
||||
"sequences",
|
||||
"tool_slots",
|
||||
"tools"
|
||||
];
|
||||
|
||||
// LEGACY API. This was a temporary solution that was superceded by the auto
|
||||
// sync feature. End of life: 1 Jan 2018
|
||||
interface DataUpdateEndOfLife {
|
||||
kind: "data_update";
|
||||
args: {
|
||||
value: string;
|
||||
};
|
||||
comment?: string | undefined;
|
||||
body?: Pair[] | undefined;
|
||||
}
|
||||
|
||||
// LEGACY API. This was a temporary solution that was superceded by the auto
|
||||
// sync feature. End of life: 1 Jan 2018
|
||||
export function notifyBotOfChanges(url: string | undefined, action: DataChangeType) {
|
||||
if (url) {
|
||||
url
|
||||
.split("/")
|
||||
.filter((chunk: ResourceName) => {
|
||||
return RESOURNCE_NAME_IN_URL.includes(chunk);
|
||||
}).map(async function (resource: ResourceName) {
|
||||
const data_update: DataUpdateEndOfLife = {
|
||||
kind: "data_update",
|
||||
args: { value: action },
|
||||
body: toPairs({ [resource]: inferUpdateId(url) })
|
||||
};
|
||||
getDevice().publish(rpcRequest([data_update as any]));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** More nasty hacks until we have time to implement proper API push state
|
||||
* notifications. */
|
||||
export function inferUpdateId(url: string) {
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
import { t } from "i18next";
|
||||
import { error } from "farmbot-toastr";
|
||||
import {
|
||||
METHODS,
|
||||
notifyBotOfChanges,
|
||||
METHOD_MAP,
|
||||
SafeError,
|
||||
isSafeError
|
||||
} from "./interceptor_support";
|
||||
|
@ -17,11 +14,7 @@ import { Dictionary } from "farmbot";
|
|||
import { outstandingRequests } from "./connectivity/data_consistency";
|
||||
|
||||
export function responseFulfilled(input: AxiosResponse): AxiosResponse {
|
||||
const method = input.config.method;
|
||||
dispatchNetworkUp("user.api");
|
||||
if (method && METHODS.includes(method)) {
|
||||
notifyBotOfChanges(input.config.url, METHOD_MAP[method]);
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
|
|
|
@ -42,8 +42,7 @@ export function generateReducer<State, U = any>(initialState: State,
|
|||
// Give the "afterEach" reducer a chance to run.
|
||||
result = (afterEach || NOOP)(defensiveClone(result), action);
|
||||
|
||||
// TODO: Do I really need to clone this?
|
||||
return defensiveClone(result);
|
||||
return result;
|
||||
}) as GeneratedReducer;
|
||||
|
||||
reducer.add = <X>(name: string, fn: ActionHandler<State, X>) => {
|
||||
|
|
|
@ -15,7 +15,8 @@ export let reducers = combineReducers({
|
|||
config,
|
||||
draggable,
|
||||
resources
|
||||
} as any); // TODO: Fix this. -RC. TSC 2.4 upgrade broke stuff.
|
||||
});
|
||||
|
||||
/** This is the topmost reducer in the application. If you need to preempt a
|
||||
* "normal" reducer this is the place to do it */
|
||||
export function rootReducer(
|
||||
|
@ -24,7 +25,7 @@ export function rootReducer(
|
|||
action: ReduxAction<{}>) {
|
||||
(action.type === Actions.LOGOUT) && Session.clear();
|
||||
|
||||
// TODO: Get rid of this nasty type case / hack. Resulted from TSC 2.4 upgrade
|
||||
// TODO: Get rid of this nasty type cast / hack. Resulted from TSC 2.4 upgrade
|
||||
// - RC 30 JUN 17
|
||||
return reducers(state, action) as Everything;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
import { Middleware } from "redux";
|
||||
|
||||
|
||||
const fn: Middleware = (x: Everything) => (dispatch) => (action: any) => {
|
||||
|
||||
// informational_settings.controller_version
|
||||
if (isResourceReady) {
|
||||
const conf: WebAppConfig = action.payload.data;
|
||||
conf.disable_i18n && revertToEnglish();
|
||||
}
|
||||
|
||||
return dispatch(action);
|
||||
};
|
|
@ -4,7 +4,6 @@ import { AuthState } from "../auth/interfaces";
|
|||
import { BotState } from "../devices/interfaces";
|
||||
import { TaggedRegimen, TaggedSequence } from "../resources/tagged_resources";
|
||||
import { ResourceIndex } from "../resources/interfaces";
|
||||
import { JSXChildren } from "../util";
|
||||
|
||||
export interface MiddleSectionProps {
|
||||
regimen: TaggedRegimen | undefined;
|
||||
|
@ -77,7 +76,7 @@ export interface RegimenItem {
|
|||
export interface AddRegimenProps {
|
||||
dispatch: Function;
|
||||
className?: string;
|
||||
children?: JSXChildren;
|
||||
children?: React.ReactNode;
|
||||
length: number;
|
||||
}
|
||||
|
||||
|
|
|
@ -19,15 +19,13 @@ export const attachAppToDom: Callback = () => {
|
|||
};
|
||||
|
||||
export class RootComponent extends React.Component<RootComponentProps, {}> {
|
||||
render() {
|
||||
// ==== TEMPORARY HACK. TODO: Add a before hook, if such a thing exists in
|
||||
// React Router. Or switch routing libs.
|
||||
componentWillMount() {
|
||||
const notLoggedIn = !Session.fetchStoredToken();
|
||||
const restrictedArea = window.location.pathname.includes("/app");
|
||||
if (notLoggedIn && restrictedArea) {
|
||||
Session.clear();
|
||||
}
|
||||
// ==== END HACK ====
|
||||
(notLoggedIn && restrictedArea && Session.clear());
|
||||
}
|
||||
|
||||
render() {
|
||||
return <Provider store={_store}>
|
||||
<Router history={history}>
|
||||
{topLevelRoutes}
|
||||
|
|
|
@ -11,7 +11,6 @@ import {
|
|||
import { StepMoveDataXfer, StepSpliceDataXfer } from "../draggable/interfaces";
|
||||
import { TaggedSequence } from "../resources/tagged_resources";
|
||||
import { ResourceIndex } from "../resources/interfaces";
|
||||
import { JSXChildren } from "../util";
|
||||
import { ShouldDisplay } from "../devices/interfaces";
|
||||
|
||||
export interface HardwareFlags {
|
||||
|
@ -89,7 +88,7 @@ export interface StepButtonParams {
|
|||
current: TaggedSequence | undefined;
|
||||
step: SequenceBodyItem;
|
||||
dispatch: Function;
|
||||
children?: JSXChildren;
|
||||
children?: React.ReactNode;
|
||||
color: "blue"
|
||||
| "green"
|
||||
| "orange"
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { ResourceIndex } from "../../../resources/interfaces";
|
||||
import { MoveAbsolute, PointType } from "farmbot/dist";
|
||||
import { JSXChildren } from "../../../util";
|
||||
import { ShouldDisplay } from "../../../devices/interfaces";
|
||||
export const TOOL: "Tool" = "Tool";
|
||||
|
||||
|
@ -15,7 +14,7 @@ export interface TileMoveAbsProps {
|
|||
|
||||
export interface InputBoxProps {
|
||||
onCommit(e: React.SyntheticEvent<HTMLInputElement>): void;
|
||||
children?: JSXChildren;
|
||||
children?: React.ReactNode;
|
||||
disabled?: boolean;
|
||||
name: string;
|
||||
value: string;
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import * as React from "react";
|
||||
import { JSXChildren } from "../../util";
|
||||
import { Row, Col } from "../../ui/index";
|
||||
|
||||
interface StepContentProps {
|
||||
children?: JSXChildren;
|
||||
children?: React.ReactNode;
|
||||
className: string;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import * as React from "react";
|
||||
import { t } from "i18next";
|
||||
import { JSXChildren } from "../../util";
|
||||
import { Row, Col } from "../../ui/index";
|
||||
import { TaggedSequence } from "../../resources/tagged_resources";
|
||||
import { SequenceBodyItem } from "farmbot";
|
||||
|
@ -9,7 +8,7 @@ import { StepIconGroup } from "../step_icon_group";
|
|||
import { splice, remove } from "../step_tiles/index";
|
||||
|
||||
export interface StepHeaderProps {
|
||||
children?: JSXChildren;
|
||||
children?: React.ReactNode;
|
||||
className: string;
|
||||
helpText: string;
|
||||
currentSequence: TaggedSequence;
|
||||
|
@ -26,7 +25,7 @@ export function StepHeader(props: StepHeaderProps) {
|
|||
currentStep,
|
||||
dispatch,
|
||||
index
|
||||
} = props;
|
||||
} = props;
|
||||
return <Row>
|
||||
<Col sm={12}>
|
||||
<div className={`step-header ${className}`} draggable={true}>
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import * as React from "react";
|
||||
import { JSXChildren } from "../../util";
|
||||
|
||||
interface StepWrapperProps {
|
||||
children?: JSXChildren;
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,14 +20,7 @@ export function mapStateToProps(props: Everything): Props {
|
|||
const tools = selectAllTools(props.resources.index);
|
||||
|
||||
/** Returns sorted tool slots specific to the tool bay id passed. */
|
||||
const getToolSlots = (/** uuid: string */) => {
|
||||
// TODO: three things:
|
||||
// 1. We don't support multiple bays. Therefore, no need to filter.
|
||||
// 2. If we add an index to this resource, we don't need to perform
|
||||
// filtering.
|
||||
// 3. Once we do support multiple bays, re-add the slot's UUID param.
|
||||
return toolSlots;
|
||||
};
|
||||
const getToolSlots = () => toolSlots;
|
||||
|
||||
/** Returns all tools in an <FBSelect /> compatible format. */
|
||||
const getToolOptions = () => {
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import * as React from "react";
|
||||
import { parseClassNames } from "./util";
|
||||
import { JSXChildren } from "../util";
|
||||
|
||||
interface ColumnProps {
|
||||
children?: JSXChildren;
|
||||
children?: React.ReactNode;
|
||||
/** {xs-col-size} */
|
||||
xs?: number;
|
||||
/** {sm-col-size} */
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import * as React from "react";
|
||||
import { JSXChildren } from "../util";
|
||||
const emoji = require("markdown-it-emoji");
|
||||
const md = require("markdown-it")({
|
||||
/** Enable HTML tags in source */
|
||||
|
@ -15,7 +14,7 @@ const md = require("markdown-it")({
|
|||
md.use(emoji);
|
||||
|
||||
interface MarkdownProps {
|
||||
children?: JSXChildren;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function Markdown(props: MarkdownProps) {
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import * as React from "react";
|
||||
import { JSXChildren } from "../util";
|
||||
|
||||
interface PageProps {
|
||||
children?: JSXChildren;
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import * as React from "react";
|
||||
import { JSXChildren } from "../util";
|
||||
|
||||
interface RowProps extends React.HTMLProps<HTMLDivElement> {
|
||||
children?: JSXChildren;
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import * as React from "react";
|
||||
import { t } from "i18next";
|
||||
import { JSXChildren } from "../util";
|
||||
|
||||
interface ToolTipProps {
|
||||
children?: JSXChildren;
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
helpText: string;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import * as React from "react";
|
||||
import { JSXChildren } from "../util";
|
||||
|
||||
interface WidgetProps {
|
||||
children?: JSXChildren;
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import * as React from "react";
|
||||
import { JSXChildren } from "../util";
|
||||
|
||||
interface WidgetBodyProps {
|
||||
children?: JSXChildren;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function WidgetBody(props: WidgetBodyProps) {
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import * as React from "react";
|
||||
import { JSXChildren } from "../util";
|
||||
|
||||
interface WidgetFooterProps {
|
||||
children?: JSXChildren;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function WidgetFooter(props: WidgetFooterProps) {
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import * as React from "react";
|
||||
import { t } from "i18next";
|
||||
import { JSXChildren } from "../util";
|
||||
import { docLink, DocSlug } from "./doc_link";
|
||||
|
||||
interface WidgetHeaderProps {
|
||||
children?: JSXChildren;
|
||||
children?: React.ReactNode;
|
||||
helpText?: string;
|
||||
docPage?: DocSlug;
|
||||
title: string;
|
||||
|
|
|
@ -135,11 +135,6 @@ export function oneOf(list: string[], target: string) {
|
|||
return !!matches;
|
||||
}
|
||||
|
||||
/** TODO: Upgrading to TSC 2.4, maybe we don't need this?
|
||||
* - RC 20 June 2016 */
|
||||
type JSXChild = JSX.Element | JSX.Element[] | Primitive | undefined;
|
||||
export type JSXChildren = JSXChild[] | JSXChild;
|
||||
|
||||
export type Primitive = boolean | string | number;
|
||||
|
||||
export function shortRevision() {
|
||||
|
|
|
@ -107,7 +107,6 @@ export function shouldDisplay(
|
|||
const target = override || current;
|
||||
if (isString(target)) {
|
||||
const min = (lookupData || {})[feature] || MinVersionOverride.NEVER;
|
||||
window.Rollbar && window.Rollbar.configure({ payload: { fbos: target } });
|
||||
switch (semverCompare(target, min)) {
|
||||
case SemverResult.LEFT_IS_GREATER:
|
||||
case SemverResult.EQUAL:
|
||||
|
|
Loading…
Reference in New Issue