Minor merge conflict in package.json while merging `staging`

pull/1125/head
Rick Carlino 2019-02-15 14:55:39 -06:00
commit 63bf7b08c7
25 changed files with 285 additions and 185 deletions

View File

@ -42,4 +42,4 @@ jobs:
- run: - run:
name: Check Coveralls coverage on staging name: Check Coveralls coverage on staging
command: | command: |
sudo docker-compose run -e CIRCLE_SHA1="$CIRCLE_SHA1" web rake coverage:run sudo docker-compose run -e CIRCLE_SHA1="$CIRCLE_SHA1" -e CIRCLE_BRANCH="$CIRCLE_BRANCH" web rake coverage:run

View File

@ -1,17 +1,13 @@
class SendNervesHubInfoJob < ApplicationJob class SendNervesHubInfoJob < ApplicationJob
WHOOPS = "SendNervesHubInfoJob Failure"
queue_as :default queue_as :default
def perform(device_id:, serial_number:, tags:) def perform(device_id:, serial_number:, tags:)
device = Device.find(device_id) device = Device.find(device_id)
DeviceSerialNumber.transaction do
device.update_attributes!(serial_number: serial_number)
resp_data = NervesHub.create_or_update(serial_number, tags) resp_data = NervesHub.create_or_update(serial_number, tags)
certs = NervesHub.sign_device(resp_data.fetch(:identifier)) certs = NervesHub.sign_device(resp_data.fetch(:identifier))
Transport.current.amqp_send(certs.to_json, device_id, "nerves_hub") Transport.current.amqp_send(certs.to_json, device_id, "nerves_hub")
end
rescue => error rescue => error
Rollbar.error(WHOOPS, { error: error, NervesHub.report_problem({ error: error,
device_id: device_id, device_id: device_id,
serial_number: serial_number, serial_number: serial_number,
tags: tags, }) tags: tags, })

View File

@ -298,4 +298,9 @@ private
def self.current_ca_file def self.current_ca_file
@current_ca_file ||= (try_env_ca_file || try_file_ca_file || nil) @current_ca_file ||= (try_env_ca_file || try_file_ca_file || nil)
end end
WHOOPS = "🚨 NervesHub Anomaly Detected! 🚨"
def self.report_problem(payload = {})
Rollbar.error(WHOOPS, payload)
end
end end

View File

@ -32,7 +32,6 @@ class Device < ApplicationRecord
has_many :diagnostic_dumps, dependent: :destroy has_many :diagnostic_dumps, dependent: :destroy
has_many :fragments, dependent: :destroy has_many :fragments, dependent: :destroy
has_one :fbos_config, dependent: :destroy has_one :fbos_config, dependent: :destroy
has_one :device_serial_number, dependent: :destroy
has_many :in_use_tools has_many :in_use_tools
has_many :in_use_points has_many :in_use_points
has_many :users has_many :users

View File

@ -1,4 +0,0 @@
class DeviceSerialNumber < ApplicationRecord
belongs_to :device
# DO NOT USE THIS TABLE. IT IS DEPRECATED. DESTROY FEB 2019.
end

View File

@ -13,7 +13,15 @@ class FbosConfig < ApplicationRecord
def sync_nerves def sync_nerves
serial = device.serial_number serial = device.serial_number
return unless serial unless serial
# This feature can be removed in May '19
# It is used to repair data damage on
# production during the initial nervehub
# deployment.
problem = "Device #{device.id} missing serial"
NervesHub.report_problem({ problem: problem })
return
end
self.delay.push_changes_to_nerves_hub(serial, update_channel) self.delay.push_changes_to_nerves_hub(serial, update_channel)
end end

View File

@ -10,7 +10,7 @@ module DeviceCerts
SendNervesHubInfoJob.perform_later(device_id: device.id, SendNervesHubInfoJob.perform_later(device_id: device.id,
serial_number: serial_number, serial_number: serial_number,
tags: tags) tags: tags)
return {} return device.update_attributes!(serial_number: serial_number) && device
end end
end end
end end

View File

@ -1,8 +1,16 @@
class DeprecateDeviceSerialNumberTable < ActiveRecord::Migration[5.2] class DeprecateDeviceSerialNumberTable < ActiveRecord::Migration[5.2]
unless Kernel.const_defined?("DeviceSerialNumber")
# Shim so that legacy users don't crash when (up|down)grading
class DeviceSerialNumber
def self.preload(*x)
end
end
end
def change def change
DeviceSerialNumber.preload(:devices) do |x| DeviceSerialNumber.preload(:devices) do |x|
x.device x.device.update_attributes!(serial_number: x.serial_number)
.update_attributes!(serial_number: x.serial_number)
end end
end end
end end

View File

@ -0,0 +1,5 @@
class RemoveDeviceSerialNumberTable < ActiveRecord::Migration[5.2]
def change
drop_table :device_serial_numbers
end
end

View File

@ -14,7 +14,6 @@ if Rails.env == "development"
[ [
Sensor, Sensor,
Peripheral, Peripheral,
DeviceSerialNumber,
Log, Log,
PinBinding, PinBinding,
Point, Point,

View File

@ -153,38 +153,6 @@ CREATE SEQUENCE public.delayed_jobs_id_seq
ALTER SEQUENCE public.delayed_jobs_id_seq OWNED BY public.delayed_jobs.id; ALTER SEQUENCE public.delayed_jobs_id_seq OWNED BY public.delayed_jobs.id;
--
-- Name: device_serial_numbers; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public.device_serial_numbers (
id bigint NOT NULL,
device_id bigint,
serial_number character varying(16) NOT NULL,
created_at timestamp without time zone NOT NULL,
updated_at timestamp without time zone NOT NULL
);
--
-- Name: device_serial_numbers_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--
CREATE SEQUENCE public.device_serial_numbers_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: device_serial_numbers_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--
ALTER SEQUENCE public.device_serial_numbers_id_seq OWNED BY public.device_serial_numbers.id;
-- --
-- Name: devices; Type: TABLE; Schema: public; Owner: - -- Name: devices; Type: TABLE; Schema: public; Owner: -
-- --
@ -385,8 +353,7 @@ CREATE TABLE public.farmware_installations (
url character varying, url character varying,
created_at timestamp without time zone NOT NULL, created_at timestamp without time zone NOT NULL,
updated_at timestamp without time zone NOT NULL, updated_at timestamp without time zone NOT NULL,
package character varying(80), package character varying(80)
package_error character varying
); );
@ -1580,13 +1547,6 @@ ALTER TABLE ONLY public.arg_sets ALTER COLUMN id SET DEFAULT nextval('public.arg
ALTER TABLE ONLY public.delayed_jobs ALTER COLUMN id SET DEFAULT nextval('public.delayed_jobs_id_seq'::regclass); ALTER TABLE ONLY public.delayed_jobs ALTER COLUMN id SET DEFAULT nextval('public.delayed_jobs_id_seq'::regclass);
--
-- Name: device_serial_numbers id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.device_serial_numbers ALTER COLUMN id SET DEFAULT nextval('public.device_serial_numbers_id_seq'::regclass);
-- --
-- Name: devices id; Type: DEFAULT; Schema: public; Owner: - -- Name: devices id; Type: DEFAULT; Schema: public; Owner: -
-- --
@ -1850,14 +1810,6 @@ ALTER TABLE ONLY public.delayed_jobs
ADD CONSTRAINT delayed_jobs_pkey PRIMARY KEY (id); ADD CONSTRAINT delayed_jobs_pkey PRIMARY KEY (id);
--
-- Name: device_serial_numbers device_serial_numbers_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.device_serial_numbers
ADD CONSTRAINT device_serial_numbers_pkey PRIMARY KEY (id);
-- --
-- Name: devices devices_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- Name: devices devices_pkey; Type: CONSTRAINT; Schema: public; Owner: -
-- --
@ -2151,13 +2103,6 @@ CREATE INDEX index_arg_sets_on_fragment_id ON public.arg_sets USING btree (fragm
CREATE INDEX index_arg_sets_on_node_id ON public.arg_sets USING btree (node_id); CREATE INDEX index_arg_sets_on_node_id ON public.arg_sets USING btree (node_id);
--
-- Name: index_device_serial_numbers_on_device_id; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX index_device_serial_numbers_on_device_id ON public.device_serial_numbers USING btree (device_id);
-- --
-- Name: index_devices_on_mounted_tool_id; Type: INDEX; Schema: public; Owner: - -- Name: index_devices_on_mounted_tool_id; Type: INDEX; Schema: public; Owner: -
-- --
@ -2720,14 +2665,6 @@ ALTER TABLE ONLY public.edge_nodes
ADD CONSTRAINT fk_rails_c86213fd78 FOREIGN KEY (sequence_id) REFERENCES public.sequences(id); ADD CONSTRAINT fk_rails_c86213fd78 FOREIGN KEY (sequence_id) REFERENCES public.sequences(id);
--
-- Name: device_serial_numbers fk_rails_d052988096; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.device_serial_numbers
ADD CONSTRAINT fk_rails_d052988096 FOREIGN KEY (device_id) REFERENCES public.devices(id);
-- --
-- Name: points fk_rails_d6f3cdbe9a; Type: FK CONSTRAINT; Schema: public; Owner: - -- Name: points fk_rails_d6f3cdbe9a; Type: FK CONSTRAINT; Schema: public; Owner: -
-- --
@ -2878,6 +2815,7 @@ INSERT INTO "schema_migrations" (version) VALUES
('20190103211708'), ('20190103211708'),
('20190103213956'), ('20190103213956'),
('20190108211419'), ('20190108211419'),
('20190209133811'); ('20190209133811'),
('20190212215842');

View File

@ -31,21 +31,23 @@ import { ready, storeToken } from "../actions";
import { setToken, didLogin } from "../../auth/actions"; import { setToken, didLogin } from "../../auth/actions";
import { Session } from "../../session"; import { Session } from "../../session";
import { auth } from "../../__test_support__/fake_state/token"; import { auth } from "../../__test_support__/fake_state/token";
import { fakeState } from "../../__test_support__/fake_state";
describe("Actions", () => { describe("Actions", () => {
it("calls didLogin()", () => { it("calls didLogin()", () => {
jest.resetAllMocks(); jest.resetAllMocks();
const dispatch = jest.fn(); const dispatch = jest.fn();
const getState = jest.fn(() => mockState);
const thunk = ready(); const thunk = ready();
thunk(dispatch, getState); thunk(dispatch, fakeState);
expect(setToken).toHaveBeenCalled(); expect(setToken).toHaveBeenCalled();
}); });
it("Calls Session.clear() when missing auth", () => { it("Calls Session.clear() when missing auth", () => {
jest.resetAllMocks(); jest.resetAllMocks();
const dispatch = jest.fn(); const dispatch = jest.fn();
const getState = jest.fn(() => ({})); const state = fakeState();
delete state.auth;
const getState = () => state;
const thunk = ready(); const thunk = ready();
thunk(dispatch, getState); thunk(dispatch, getState);
expect(Session.clear).toHaveBeenCalled(); expect(Session.clear).toHaveBeenCalled();

View File

@ -1,5 +1,6 @@
import { toggleWebAppBool } from "../actions"; import { toggleWebAppBool } from "../actions";
import { BooleanSetting } from "../../session_keys"; import { BooleanSetting } from "../../session_keys";
import { fakeState } from "../../__test_support__/fake_state";
jest.mock("../../api/crud", () => { jest.mock("../../api/crud", () => {
return { save: jest.fn(), edit: jest.fn() }; return { save: jest.fn(), edit: jest.fn() };
@ -13,8 +14,7 @@ describe("toggleWebAppBool", () => {
it("toggles things", () => { it("toggles things", () => {
const action = toggleWebAppBool(BooleanSetting.show_first_party_farmware); const action = toggleWebAppBool(BooleanSetting.show_first_party_farmware);
const dispatch = jest.fn(); const dispatch = jest.fn();
const getState = jest.fn(() => ({ resources: { index: {} } })); const kaboom = () => action(dispatch, fakeState);
const kaboom = () => action(dispatch, getState);
expect(kaboom).toThrowError("Toggled settings before app was loaded."); expect(kaboom).toThrowError("Toggled settings before app was loaded.");
}); });
}); });

View File

@ -1,7 +1,10 @@
import { toggleWebAppBool, getWebAppConfigValue, setWebAppConfigValue } from "../actions"; import {
toggleWebAppBool, getWebAppConfigValue, setWebAppConfigValue
} from "../actions";
import { BooleanSetting, NumericSetting } from "../../session_keys"; import { BooleanSetting, NumericSetting } from "../../session_keys";
import { edit, save } from "../../api/crud"; import { edit, save } from "../../api/crud";
import { fakeWebAppConfig } from "../../__test_support__/fake_state/resources"; import { fakeWebAppConfig } from "../../__test_support__/fake_state/resources";
import { fakeState } from "../../__test_support__/fake_state";
jest.mock("../../api/crud", () => { jest.mock("../../api/crud", () => {
return { save: jest.fn(), edit: jest.fn() }; return { save: jest.fn(), edit: jest.fn() };
@ -18,8 +21,7 @@ describe("toggleWebAppBool", () => {
it("toggles things", () => { it("toggles things", () => {
const action = toggleWebAppBool(BooleanSetting.show_first_party_farmware); const action = toggleWebAppBool(BooleanSetting.show_first_party_farmware);
const dispatch = jest.fn(); const dispatch = jest.fn();
const getState = jest.fn(() => ({ resources: { index: {} } })); action(dispatch, fakeState);
action(dispatch, getState);
expect(edit).toHaveBeenCalledWith(mockConfig, { expect(edit).toHaveBeenCalledWith(mockConfig, {
show_first_party_farmware: true show_first_party_farmware: true
}); });
@ -28,8 +30,7 @@ describe("toggleWebAppBool", () => {
}); });
describe("getWebAppConfigValue", () => { describe("getWebAppConfigValue", () => {
const getState = jest.fn(() => ({ resources: { index: {} } })); const getValue = getWebAppConfigValue(fakeState);
const getValue = getWebAppConfigValue(getState);
it("gets a boolean setting value", () => { it("gets a boolean setting value", () => {
expect(getValue(BooleanSetting.show_first_party_farmware)).toEqual(false); expect(getValue(BooleanSetting.show_first_party_farmware)).toEqual(false);
@ -41,10 +42,8 @@ describe("getWebAppConfigValue", () => {
}); });
describe("setWebAppConfigValue", () => { describe("setWebAppConfigValue", () => {
const getState = jest.fn(() => ({ resources: { index: {} } }));
it("sets a numeric setting value", () => { it("sets a numeric setting value", () => {
setWebAppConfigValue(NumericSetting.fun_log, 2)(jest.fn(), getState); setWebAppConfigValue(NumericSetting.fun_log, 2)(jest.fn(), fakeState);
expect(edit).toHaveBeenCalledWith(mockConfig, { fun_log: 2 }); expect(edit).toHaveBeenCalledWith(mockConfig, { fun_log: 2 });
expect(save).toHaveBeenCalledWith(mockConfig.uuid); expect(save).toHaveBeenCalledWith(mockConfig.uuid);
}); });
@ -53,7 +52,7 @@ describe("setWebAppConfigValue", () => {
// tslint:disable-next-line:no-any // tslint:disable-next-line:no-any
mockConfig = undefined as any; mockConfig = undefined as any;
const action = () => setWebAppConfigValue(NumericSetting.fun_log, 1)( const action = () => setWebAppConfigValue(NumericSetting.fun_log, 1)(
jest.fn(), getState); jest.fn(), fakeState);
expect(action).toThrowError("Changed settings before app was loaded."); expect(action).toThrowError("Changed settings before app was loaded.");
}); });
}); });

View File

@ -222,7 +222,7 @@ describe("settingToggle()", () => {
window.alert = jest.fn(); window.alert = jest.fn();
const msg = "this is an alert."; const msg = "this is an alert.";
actions.settingToggle( actions.settingToggle(
"param_mov_nr_retry", jest.fn(() => ({ value: "" })), "param_mov_nr_retry", jest.fn(() => ({ value: 1, consistent: true })),
msg)(jest.fn(), fakeState); msg)(jest.fn(), fakeState);
expect(window.alert).toHaveBeenCalledWith(msg); expect(window.alert).toHaveBeenCalledWith(msg);
}); });

View File

@ -10,7 +10,7 @@ describe("onInit()", () => {
await onInit({}, jest.fn()); await onInit({}, jest.fn());
expect({}).toBeTruthy(); expect({}).toBeTruthy();
expect(render).toHaveBeenCalled(); expect(render).toHaveBeenCalled();
const [calls] = (render as jest.Mock<{}>).mock.calls; const [calls] = (render as jest.Mock).mock.calls;
expect(calls[0].type.name).toBe("PasswordReset"); expect(calls[0].type.name).toBe("PasswordReset");
done(); done();
}); });

View File

@ -1,14 +1,64 @@
COVERAGE_FILE_PATH = "./coverage_fe/index.html" COVERAGE_FILE_PATH = "./coverage_fe/index.html"
THRESHOLD = 0.001 THRESHOLD = 0.001
REPO_URL = "https://api.github.com/repos/Farmbot/Farmbot-Web-App/git"\ REPO_URL = "https://api.github.com/repos/Farmbot/Farmbot-Web-App"
"/refs/heads/staging" CURRENT_BRANCH = ENV.fetch("CIRCLE_BRANCH", "staging")
CURRENT_COMMIT = ENV.fetch("CIRCLE_SHA1", "") CURRENT_COMMIT = ENV.fetch("CIRCLE_SHA1", "")
CSS_SELECTOR = ".fraction" CSS_SELECTOR = ".fraction"
FRACTION_DELIM = "/" FRACTION_DELIM = "/"
# Fetch JSON over HTTP. Rails probably already has a helper for this :shrug: # Fetch JSON over HTTP. Rails probably already has a helper for this :shrug:
def open_json(url) def open_json(url)
begin
JSON.parse(open(url).read) JSON.parse(open(url).read)
rescue OpenURI::HTTPError => exception
puts exception.message
return {}
end
end
# Get pull request information from the GitHub API.
def fetch_pull_data()
if CURRENT_BRANCH.include? "/"
return open_json("#{REPO_URL}/pulls/#{CURRENT_BRANCH.split("/")[1]}")
end
return {}
end
# Determine the base branch of the current build.
def get_current_branch(pull_data)
pull_data.dig("base", "ref") || CURRENT_BRANCH
end
# Assemble the coverage data URL using the provided branch.
def coverage_url(branch)
commit = open_json("#{REPO_URL}/git/refs/heads/#{branch}").dig("object", "sha")
return "https://coveralls.io/builds/#{commit}.json"
end
# Fetch relevant remote coverage data.
def fetch_build_data(url)
build_data = open_json(url)
return {
branch: build_data["branch"],
commit: build_data["commit_sha"],
percent: build_data["covered_percent"]}
end
# <commit hash> on <username>:<branch>
def branch_info_string?(target, pull_data)
unless pull_data.dig(target, "sha").nil?
"#{pull_data.dig(target, "sha")} on #{pull_data.dig(target, "label")}"
end
end
# Print a coverage difference summary string.
def print_summary_text(build_percent, remote, pull_data)
diff = (build_percent - remote[:percent]).round(2)
direction = diff > 0 ? "increased" : "decreased"
description = diff == 0 ? "remained the same at" : "#{direction} (#{diff}%) to"
puts "Coverage #{description} #{build_percent.round(3)}%"\
" when pulling #{branch_info_string?("head", pull_data)}"\
" into #{branch_info_string?("base", pull_data) || remote[:branch]}."
end end
def to_percent(pair) def to_percent(pair)
@ -18,6 +68,7 @@ end
namespace :coverage do namespace :coverage do
desc "Coveralls stats stopped working :(" desc "Coveralls stats stopped working :("
task run: :environment do task run: :environment do
# Fetch current build coverage data from the HTML summary.
statements, branches, functions, lines = Nokogiri::HTML(open(COVERAGE_FILE_PATH)) statements, branches, functions, lines = Nokogiri::HTML(open(COVERAGE_FILE_PATH))
.css(CSS_SELECTOR) .css(CSS_SELECTOR)
.map(&:text) .map(&:text)
@ -30,41 +81,66 @@ namespace :coverage do
puts "Branches: #{to_percent(branches)}%" puts "Branches: #{to_percent(branches)}%"
puts "Functions: #{to_percent(functions)}%" puts "Functions: #{to_percent(functions)}%"
puts "Lines: #{to_percent(lines)}%" puts "Lines: #{to_percent(lines)}%"
puts
# Calculate an aggregate coverage percentage for the current build.
covered = lines.head + branches.head covered = lines.head + branches.head
total = lines.tail + branches.tail total = lines.tail + branches.tail
build_percent = (covered / total) * 100 build_percent = (covered / total) * 100
puts "Aggregate: #{build_percent.round(4)}%"
puts
latest_commit_staging = open_json(REPO_URL).dig("object", "sha") # Attempt to fetch remote build coverage data for the current branch.
puts "staging: #{latest_commit_staging}" pull_request_data = fetch_pull_data()
build_url = "https://coveralls.io/builds/#{latest_commit_staging}.json" current_branch = get_current_branch(pull_request_data)
any_build_url = "https://coveralls.io/github/FarmBot/Farmbot-Web-App.json" remote = fetch_build_data(coverage_url(current_branch))
begin
staging_percent = open_json(build_url).fetch("covered_percent") || if remote[:percent].nil? && CURRENT_COMMIT == remote[:commit]
open_json(any_build_url).fetch("covered_percent") puts "Coverage already calculated for #{remote[:branch]}."
rescue OpenURI::HTTPError => exception puts "Using this build's data instead."
puts exception.message remote[:percent] = build_percent
end
if remote[:percent].nil? && current_branch != "staging"
puts "Error getting coveralls data for #{current_branch}."
puts "Attempting to use staging build coveralls data."
remote = fetch_build_data(coverage_url("staging"))
end
if remote[:percent].nil?
puts "Error getting coveralls data for staging."
puts "Attempting to use latest build coveralls data."
latest_cov_url = "https://coveralls.io/github/FarmBot/Farmbot-Web-App.json"
remote = fetch_build_data(latest_cov_url)
end
if remote[:percent].nil?
puts "Error getting coveralls data." puts "Error getting coveralls data."
puts "Wait for staging build to finish and try again." puts "Wait for build to finish and try again or check for build errors."
puts "If error continues, perhaps a blinky test failed the staging build." puts "Using 100 instead of nil for remote coverage value."
staging_percent = 100 remote = {branch: "N/A", commit: "", percent: 100}
end end
if CURRENT_COMMIT == latest_commit_staging # Adjust remote build data values for printing.
staging_percent = build_percent r = {
end branch: (remote[:branch] + ' ' * 8)[0,8],
percent: remote[:percent].round(8),
commit: remote[:commit][0,8]}
diff = (build_percent - staging_percent) # Calculate coverage difference between the current and previous build.
diff = (build_percent - remote[:percent])
pass = (diff > -THRESHOLD) pass = (diff > -THRESHOLD)
puts
puts "=" * 37 puts "=" * 37
puts "COVERAGE RESULTS" puts "COVERAGE RESULTS"
puts "This build: #{build_percent.round(8)}% #{CURRENT_COMMIT[0,8]}" puts "This build: #{build_percent.round(8)}% #{CURRENT_COMMIT[0,8]}"
puts "Staging build: #{staging_percent.round(8)}% #{latest_commit_staging[0,8]}" puts "#{r[:branch]} build: #{r[:percent]}% #{r[:commit]}"
puts "=" * 37 puts "=" * 37
puts "Difference: #{diff.round(8)}%" puts "Difference: #{diff.round(8)}%"
puts "Pass?: #{pass ? "yes" : "no"}" puts "Pass? #{pass ? "yes" : "no"}"
puts
print_summary_text(build_percent, remote, pull_request_data)
exit pass ? 0 : 1 exit pass ? 0 : 1

69
lib/tasks/fe.rake 100644
View File

@ -0,0 +1,69 @@
PACKAGE_JSON_FILE = "./package.json"
DEPS_KEY = "dependencies"
EXCLUDE = ["i18next", "@types/i18next"]
# Load package.json as JSON.
def load_package_json()
return JSON.parse(open(PACKAGE_JSON_FILE).read)
end
# Save JSON to package.json.
def save_package_json(json)
open(PACKAGE_JSON_FILE, "w") { |file|
file.write(JSON.pretty_generate(json))
file.puts
}
end
# Fetch latest versions for outdated dependencies.
def fetch_available_upgrades()
begin
latest_json = JSON.parse(`npm outdated --json`)
rescue JSON::ParserError => exception
latest_json = {}
end
latest_versions = {}
latest_json.each do |dep, data|
unless EXCLUDE.include?(dep) || data["latest"].nil?
latest_versions[dep] = data["latest"]
end
end
return latest_versions
end
# Install depdendency updates.
def install_updates
sh "sudo docker-compose run web npm install"
end
namespace :fe do
desc "Update frontend dependencies to the latest available."\
"This often causes breakage. Use only for development."
task update_deps: :environment do
puts "begin?"; if !user_typed?("developer"); puts "done."; exit end
available_upgrades = fetch_available_upgrades()
if available_upgrades.length > 0
max_key_length = available_upgrades.keys.max_by(&:length).length
package_json = load_package_json()
puts
puts "=" * 40
puts "#{PACKAGE_JSON_FILE} AVAILABLE UPDATES:"
available_upgrades.each do |dep, new_version|
current_version = package_json[DEPS_KEY][dep]
padding = ' ' * (max_key_length - dep.length)
puts " #{dep} #{padding} #{current_version} -> #{new_version}"
package_json[DEPS_KEY][dep] = new_version
end
puts "=" * 40
puts "Type 'save' to update #{PACKAGE_JSON_FILE}, enter to abort."
save_package_json(package_json) if user_typed?("save")
puts "Saved. Use 'sudo docker-compose run web npm install' to upgrade."
# install_updates if user_typed?("update")
else
puts "No updates available."
end
end
end

View File

@ -24,47 +24,48 @@
"author": "farmbot.io", "author": "farmbot.io",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@blueprintjs/core": "3.10.0", "@blueprintjs/core": "3.13.0",
"@blueprintjs/datetime": "3.5.0", "@blueprintjs/datetime": "3.7.1",
"@blueprintjs/select": "3.4.0", "@blueprintjs/select": "3.6.1",
"@types/enzyme": "3.1.15", "@types/enzyme": "3.1.18",
"@types/jest": "23.3.12", "@types/i18next": "12.1.0",
"@types/lodash": "4.14.120", "@types/jest": "24.0.5",
"@types/lodash": "4.14.121",
"@types/markdown-it": "0.0.7", "@types/markdown-it": "0.0.7",
"@types/moxios": "0.4.8", "@types/moxios": "0.4.8",
"@types/node": "10.12.18", "@types/node": "11.9.4",
"@types/promise-timeout": "1.3.0", "@types/promise-timeout": "1.3.0",
"@types/react": "16.7.18", "@types/react": "16.8.3",
"@types/react-color": "2.14.0", "@types/react-color": "2.14.1",
"@types/react-dom": "16.0.11", "@types/react-dom": "16.8.1",
"@types/react-redux": "6.0.12", "@types/react-redux": "7.0.1",
"axios": "0.18.0", "axios": "0.18.0",
"boxed_value": "1.0.0", "boxed_value": "1.0.0",
"browser-speech": "1.1.1", "browser-speech": "1.1.1",
"coveralls": "3.0.2", "coveralls": "3.0.2",
"enzyme": "3.8.0", "enzyme": "3.8.0",
"enzyme-adapter-react-16": "1.7.1", "enzyme-adapter-react-16": "1.9.1",
"farmbot": "7.0.0-rc7", "farmbot": "7.0.0-rc7",
"farmbot-toastr": "1.0.3", "farmbot-toastr": "1.0.3",
"i18next": "13.1.0", "i18next": "12.1.0",
"jest": "23.6.0", "jest": "24.1.0",
"jest-cli": "23.6.0", "jest-cli": "24.1.0",
"lodash": "4.17.11", "lodash": "4.17.11",
"markdown-it": "8.4.2", "markdown-it": "8.4.2",
"markdown-it-emoji": "1.4.0", "markdown-it-emoji": "1.4.0",
"moment": "2.23.0", "moment": "2.24.0",
"moxios": "0.4.0", "moxios": "0.4.0",
"parcel-bundler": "1.11.0", "parcel-bundler": "1.11.0",
"promise-timeout": "1.3.0", "promise-timeout": "1.3.0",
"raf": "3.4.1", "raf": "3.4.1",
"react": "16.7.0", "react": "16.8.2",
"react-addons-test-utils": "15.6.2", "react-addons-test-utils": "15.6.2",
"react-color": "2.17.0", "react-color": "2.17.0",
"react-dom": "16.7.0", "react-dom": "16.8.2",
"react-joyride": "2.0.2", "react-joyride": "2.0.5",
"react-redux": "6.0.0", "react-redux": "6.0.0",
"react-test-renderer": "16.7.0", "react-test-renderer": "16.8.2",
"react-transition-group": "2.5.2", "react-transition-group": "2.5.3",
"redux": "4.0.1", "redux": "4.0.1",
"redux-immutable-state-invariant": "2.1.0", "redux-immutable-state-invariant": "2.1.0",
"redux-thunk": "2.3.0", "redux-thunk": "2.3.0",
@ -72,14 +73,14 @@
"takeme": "0.11.1", "takeme": "0.11.1",
"ts-jest": "23.10.5", "ts-jest": "23.10.5",
"ts-lint": "4.5.1", "ts-lint": "4.5.1",
"tslint": "5.12.0", "tslint": "5.12.1",
"typescript": "3.2.4", "typescript": "3.3.3",
"which": "1.3.1" "which": "1.3.1"
}, },
"devDependencies": { "devDependencies": {
"jest-skipped-reporter": "0.0.4", "jest-skipped-reporter": "0.0.4",
"madge": "3.4.3", "madge": "3.4.4",
"sass": "1.16.1" "sass": "1.17.0"
}, },
"jest": { "jest": {
"clearMocks": true, "clearMocks": true,
@ -130,6 +131,8 @@
"lcov" "lcov"
], ],
"coverageDirectory": "<rootDir>/coverage_fe", "coverageDirectory": "<rootDir>/coverage_fe",
"setupTestFrameworkScriptFile": "<rootDir>/frontend/__test_support__/customMatchers.js" "setupFilesAfterEnv": [
"<rootDir>/frontend/__test_support__/customMatchers.js"
]
} }
} }

View File

@ -34,7 +34,6 @@ describe Api::DeviceCertsController do
post_args = ["/orgs/farmbot/devices/456/certificates/sign", post_args = ["/orgs/farmbot/devices/456/certificates/sign",
post_data, post_data,
{"Content-Type"=>"application/json"}] {"Content-Type"=>"application/json"}]
old_count = DeviceSerialNumber.count
# Setup wiring =========================================================== # Setup wiring ===========================================================
NervesHub.set_conn(conn) NervesHub.set_conn(conn)
@ -57,8 +56,8 @@ describe Api::DeviceCertsController do
post :create, body: payl.to_json, params: {format: :json} post :create, body: payl.to_json, params: {format: :json}
end end
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(json).to eq({}) expect(json).to be_kind_of(Hash)
expect(DeviceSerialNumber.count).to eq(old_count) expect(json.fetch(:id)).to eq(device.id)
end end
end end
end end

View File

@ -1,6 +0,0 @@
FactoryBot.define do
factory :device_serial_number do
device { nil }
serial_number { raise "Stop using this model" }
end
end

View File

@ -1,17 +1,31 @@
require 'spec_helper' require "spec_helper"
describe FbosConfig do describe FbosConfig do
let(:device) { FactoryBot.create(:device) } let(:device) { FactoryBot.create(:device) }
let(:config) { FbosConfig.create!(device: device) } let(:config) { FbosConfig.create!(device: device) }
it 'triggers callbacks' do def fake_conn(desc)
conn = double("Create a cert", :ca_file= => nil, double(desc, :ca_file= => nil,
:cert_store => nil, :cert_store => nil,
:cert_store= => nil, :cert_store= => nil,
:use_ssl => nil, :use_ssl => nil,
:use_ssl= => nil, :use_ssl= => nil,
:cert= => nil, :cert= => nil,
:key= => nil) :key= => nil)
end
it "notifies us of broken production data" do
# Remove this test by May 2019.
config.device.update_attributes!(serial_number: nil)
conn = fake_conn("Report broke data")
NervesHub.set_conn(conn)
problem = "Device #{device.id} missing serial"
expect(NervesHub).to receive(:report_problem).with({ problem: problem })
config.sync_nerves
end
it "triggers callbacks" do
conn = fake_conn("Create a cert")
NervesHub.set_conn(conn) NervesHub.set_conn(conn)
url = "/orgs/farmbot/devices/#{device.serial_number}" url = "/orgs/farmbot/devices/#{device.serial_number}"
resp = StubResp.new("200", { "data" => { "tags": [] } }.to_json) resp = StubResp.new("200", { "data" => { "tags": [] } }.to_json)

View File

@ -33,7 +33,7 @@ describe DeviceCerts::Create do
result = DeviceCerts::Create.run!(tags: tags, result = DeviceCerts::Create.run!(tags: tags,
device: device, device: device,
serial_number: ser) serial_number: ser)
expect(result).to eq({}) expect(result).to eq(device)
end end
end end
end end

View File

@ -1,9 +0,0 @@
import i18n from "i18next";
declare module "i18next" {
export function init(options: i18n.InitOptions, callback?: i18n.Callback):
Promise<i18n.TranslationFunction>;
export const t: i18n.TranslationFunction;
export type InitOptions = i18n.InitOptions;
export type Callback = i18n.Callback;
}

1
typings/index.d.ts vendored
View File

@ -1,5 +1,4 @@
/// <reference path="react-redux.d.ts" /> /// <reference path="react-redux.d.ts" />
/// <reference path="i18next.d.ts" />
/** This contains all of the global ENV vars passed from server => client. /** This contains all of the global ENV vars passed from server => client.
* Previously was `process.env.XYZ`. */ * Previously was `process.env.XYZ`. */