Merge conflicts

pull/736/head
Rick Carlino 2018-03-19 13:39:17 -05:00
commit fb80d4c597
53 changed files with 599 additions and 2353 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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 }

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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": {

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()
}));

View File

@ -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}

View File

@ -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) {

View File

@ -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) {

View File

@ -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;
})

View File

@ -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;
}

View File

@ -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>

View File

@ -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));
}));

View File

@ -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):

View File

@ -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;

View File

@ -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) {

View File

@ -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;
}

View File

@ -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>) => {

View File

@ -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;
}

View File

@ -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);
};

View File

@ -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;
}

View File

@ -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}

View File

@ -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"

View File

@ -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;

View File

@ -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;
}

View File

@ -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}>

View File

@ -1,8 +1,7 @@
import * as React from "react";
import { JSXChildren } from "../../util";
interface StepWrapperProps {
children?: JSXChildren;
children?: React.ReactNode;
className?: string;
}

View File

@ -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 = () => {

View File

@ -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} */

View File

@ -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) {

View File

@ -1,8 +1,7 @@
import * as React from "react";
import { JSXChildren } from "../util";
interface PageProps {
children?: JSXChildren;
children?: React.ReactNode;
className?: string;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -1,8 +1,7 @@
import * as React from "react";
import { JSXChildren } from "../util";
interface WidgetProps {
children?: JSXChildren;
children?: React.ReactNode;
className?: string;
}

View File

@ -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) {

View File

@ -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) {

View File

@ -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;

View File

@ -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() {

View File

@ -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:

2364
yarn.lock

File diff suppressed because it is too large Load Diff