Minor merge conflict in package.json while merging `staging`
commit
63bf7b08c7
|
@ -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
|
||||||
|
|
|
@ -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, })
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
class DeviceSerialNumber < ApplicationRecord
|
|
||||||
belongs_to :device
|
|
||||||
# DO NOT USE THIS TABLE. IT IS DEPRECATED. DESTROY FEB 2019.
|
|
||||||
end
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
class RemoveDeviceSerialNumberTable < ActiveRecord::Migration[5.2]
|
||||||
|
def change
|
||||||
|
drop_table :device_serial_numbers
|
||||||
|
end
|
||||||
|
end
|
|
@ -14,7 +14,6 @@ if Rails.env == "development"
|
||||||
[
|
[
|
||||||
Sensor,
|
Sensor,
|
||||||
Peripheral,
|
Peripheral,
|
||||||
DeviceSerialNumber,
|
|
||||||
Log,
|
Log,
|
||||||
PinBinding,
|
PinBinding,
|
||||||
Point,
|
Point,
|
||||||
|
|
|
@ -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');
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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.");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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.");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
|
@ -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();
|
||||||
});
|
});
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
55
package.json
55
package.json
|
@ -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"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
FactoryBot.define do
|
|
||||||
factory :device_serial_number do
|
|
||||||
device { nil }
|
|
||||||
serial_number { raise "Stop using this model" }
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,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`. */
|
||||||
|
|
Loading…
Reference in New Issue