Merge branch 'staging' into cosmetic-fixes
commit
29ecee243c
|
@ -1,12 +1,10 @@
|
|||
module Api
|
||||
class DiagnosticDumpsController < Api::AbstractController
|
||||
|
||||
def index
|
||||
render json: diagnostic_dumps
|
||||
end
|
||||
|
||||
def create
|
||||
Rollbar.info("Device #{current_device.id} created a diagnostic")
|
||||
mutate DiagnosticDumps::Create.run(raw_json, device: current_device)
|
||||
end
|
||||
|
||||
|
@ -15,7 +13,7 @@ module Api
|
|||
render json: ""
|
||||
end
|
||||
|
||||
private
|
||||
private
|
||||
|
||||
def diagnostic_dumps
|
||||
current_device.diagnostic_dumps
|
||||
|
|
|
@ -94,9 +94,6 @@ class DashboardController < ApplicationController
|
|||
rescue
|
||||
report = { problem: "Crashed while parsing report" }
|
||||
end
|
||||
# We get too many CSP reports.
|
||||
# Rollbar.info("CSP Violation", report)
|
||||
|
||||
render json: report
|
||||
end
|
||||
|
||||
|
|
|
@ -89,6 +89,7 @@ class ApplicationRecord < ActiveRecord::Base
|
|||
current_device.id,
|
||||
chan_name,
|
||||
Time.now.utc.to_i) if current_device
|
||||
self
|
||||
end
|
||||
|
||||
def manually_sync!
|
||||
|
|
|
@ -45,11 +45,9 @@ class User < ApplicationRecord
|
|||
end
|
||||
|
||||
def self.refresh_everyones_ui
|
||||
Rollbar.error("Global UI refresh triggered")
|
||||
|
||||
msg = {
|
||||
"type" => "reload",
|
||||
"commit" => (ENV["HEROKU_SLUG_COMMIT"] || "NONE").first(8)
|
||||
"commit" => (ENV["HEROKU_SLUG_COMMIT"] || "NONE").first(8),
|
||||
}
|
||||
|
||||
Transport
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
module PointGroups
|
||||
class Update < Mutations::Command
|
||||
include PointGroups::Helpers
|
||||
BLACKLISTED_FIELDS = [:device, :point_ids, :point_group]
|
||||
|
||||
required do
|
||||
model :device, class: Device
|
||||
|
@ -19,11 +20,13 @@ module PointGroups
|
|||
end
|
||||
|
||||
def execute
|
||||
PointGroup.transaction do
|
||||
PointGroup.auto_sync_debounce do
|
||||
PointGroup.transaction do
|
||||
maybe_reconcile_points
|
||||
point_group.update_attributes!(update_attributes)
|
||||
point_group.reload
|
||||
PointGroupItem.transaction do
|
||||
maybe_reconcile_points
|
||||
point_group.update_attributes!(update_attributes)
|
||||
point_group.reload # <= Because PointGroupItem caching?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -31,9 +34,7 @@ module PointGroups
|
|||
private
|
||||
|
||||
def update_attributes
|
||||
@update_attributes ||= inputs
|
||||
.except(:device, :point_ids, :point_group)
|
||||
.merge(updated_at: Time.now)
|
||||
@update_attributes ||= inputs.except(*BLACKLISTED_FIELDS)
|
||||
end
|
||||
|
||||
def maybe_reconcile_points
|
||||
|
|
24
db/seeds.rb
24
db/seeds.rb
|
@ -63,15 +63,25 @@ if Rails.env == "development"
|
|||
z: rand(1...300) })
|
||||
end
|
||||
|
||||
VEGGIES.shuffle.first(PLANT_COUNT).each do |veggie|
|
||||
Plant.create(device: u.device,
|
||||
x: rand(40...1500),
|
||||
y: rand(40...800),
|
||||
radius: rand(30...60),
|
||||
name: veggie,
|
||||
openfarm_slug: veggie.downcase.gsub(" ", "-"))
|
||||
all_of_em = []
|
||||
1.upto(8) do |n1|
|
||||
1.upto(8) do |n2|
|
||||
veggie = VEGGIES.sample
|
||||
p = Plant.create(device: u.device,
|
||||
x: n1 * 80,
|
||||
y: n2 * 80,
|
||||
radius: rand(20...70),
|
||||
name: veggie,
|
||||
openfarm_slug: veggie.downcase.gsub(" ", "-"))
|
||||
all_of_em.push(p.id)
|
||||
end
|
||||
end
|
||||
|
||||
PointGroups::Create.run!(device: u.device,
|
||||
name: "TEST GROUP I",
|
||||
point_ids: all_of_em.sample(8),
|
||||
sort_type: "random")
|
||||
|
||||
Device.all.map { |device| SavedGardens::Snapshot.run!(device: device) }
|
||||
|
||||
POINT_COUNT.times do
|
||||
|
|
|
@ -77,6 +77,16 @@ export class FarmbotOsSettings
|
|||
const { bot, sourceFbosConfig, botToMqttStatus } = this.props;
|
||||
const { sync_status } = bot.hardware.informational_settings;
|
||||
const botOnline = isBotOnline(sync_status, botToMqttStatus);
|
||||
const bootRow = <Row>
|
||||
<Col xs={ColWidth.label}>
|
||||
<label>
|
||||
{t("BOOT SEQUENCE")}
|
||||
</label>
|
||||
</Col>
|
||||
<Col xs={7}>
|
||||
<BootSequenceSelector />
|
||||
</Col>
|
||||
</Row>;
|
||||
return <Widget className="device-widget">
|
||||
<form onSubmit={(e) => e.preventDefault()}>
|
||||
<WidgetHeader title="Device">
|
||||
|
@ -150,16 +160,7 @@ export class FarmbotOsSettings
|
|||
shouldDisplay={this.props.shouldDisplay}
|
||||
timeSettings={this.props.timeSettings}
|
||||
sourceFbosConfig={sourceFbosConfig} />
|
||||
<Row>
|
||||
<Col xs={ColWidth.label}>
|
||||
<label>
|
||||
{t("BOOT SEQUENCE")}
|
||||
</label>
|
||||
</Col>
|
||||
<Col xs={7}>
|
||||
{this.props.shouldDisplay(Feature.boot_sequence) && <BootSequenceSelector />}
|
||||
</Col>
|
||||
</Row>
|
||||
{this.props.shouldDisplay(Feature.boot_sequence) && bootRow}
|
||||
<PowerAndReset
|
||||
controlPanelState={this.props.bot.controlPanelState}
|
||||
dispatch={this.props.dispatch}
|
||||
|
|
|
@ -71,4 +71,14 @@ describe("<GroupDetailActive/>", () => {
|
|||
},
|
||||
{ sort_type: "random" });
|
||||
});
|
||||
|
||||
it("unmounts", () => {
|
||||
window.clearInterval = jest.fn();
|
||||
const p = fakeProps();
|
||||
const el = new GroupDetailActive(p);
|
||||
// tslint:disable-next-line:no-any
|
||||
el.state.timerId = 123 as any;
|
||||
el.componentWillUnmount && el.componentWillUnmount();
|
||||
expect(clearInterval).toHaveBeenCalledWith(123);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,8 +9,6 @@ import {
|
|||
import { TaggedPointGroup } from "farmbot";
|
||||
import { DeleteButton } from "../../controls/pin_form_fields";
|
||||
import { save, edit } from "../../api/crud";
|
||||
import { Dictionary } from "lodash";
|
||||
import { OFIcon } from "../../open_farm/cached_crop";
|
||||
import { TaggedPlant } from "../map/interfaces";
|
||||
import { PointGroupSortSelector, sortGroupBy } from "./point_group_sort_selector";
|
||||
import { PointGroupSortType } from "farmbot/dist/resources/api_resources";
|
||||
|
@ -22,7 +20,7 @@ interface GroupDetailActiveProps {
|
|||
plants: TaggedPlant[];
|
||||
}
|
||||
|
||||
type State = Dictionary<OFIcon | undefined>;
|
||||
type State = { timerId?: ReturnType<typeof setInterval> };
|
||||
|
||||
export class GroupDetailActive
|
||||
extends React.Component<GroupDetailActiveProps, State> {
|
||||
|
@ -61,6 +59,16 @@ export class GroupDetailActive
|
|||
dispatch(edit(group, { sort_type }));
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// There are better ways to do this.
|
||||
this.setState({ timerId: setInterval(this.saveGroup, 900) });
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
const { timerId } = this.state;
|
||||
(typeof timerId == "number") && clearInterval(timerId);
|
||||
}
|
||||
|
||||
render() {
|
||||
return <DesignerPanel panelName={"groups"} panelColor={"blue"}>
|
||||
<DesignerPanelHeader
|
||||
|
|
|
@ -5,7 +5,7 @@ import { AddButton } from "../add_button";
|
|||
|
||||
describe("<AddButton />", () => {
|
||||
it("renders an add button when active", () => {
|
||||
const props: AddButtonProps = { active: true, click: jest.fn() };
|
||||
const props: AddButtonProps = { active: true, onClick: jest.fn() };
|
||||
const wrapper = mount(<AddButton {...props} />);
|
||||
const button = wrapper.find("button");
|
||||
["green", "add"].map(klass => {
|
||||
|
@ -13,11 +13,11 @@ describe("<AddButton />", () => {
|
|||
});
|
||||
expect(wrapper.find("i").hasClass("fa-plus")).toBeTruthy();
|
||||
button.simulate("click");
|
||||
expect(props.click).toHaveBeenCalled();
|
||||
expect(props.onClick).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("renders a <div> when inactive", () => {
|
||||
const props: AddButtonProps = { active: false, click: jest.fn() };
|
||||
const props: AddButtonProps = { active: false, onClick: jest.fn() };
|
||||
const wrapper = mount(<AddButton {...props} />);
|
||||
expect(wrapper.html()).toEqual("<div></div>");
|
||||
});
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
} from "../../../__test_support__/resource_index_builder";
|
||||
import { Actions } from "../../../constants";
|
||||
import { fakeSequence } from "../../../__test_support__/fake_state/resources";
|
||||
import { AddButton } from "../add_button";
|
||||
|
||||
describe("<BulkScheduler />", () => {
|
||||
const weeks = [{
|
||||
|
@ -102,4 +103,12 @@ describe("<BulkScheduler />", () => {
|
|||
expect(change).toThrowError("WARNING: Not a sequence UUID.");
|
||||
expect(p.dispatch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("commits bulk editor", () => {
|
||||
const p = fakeProps();
|
||||
p.dispatch = jest.fn();
|
||||
const panel = shallow<BulkScheduler>(<BulkScheduler {...p} />);
|
||||
panel.find(AddButton).first().simulate("click");
|
||||
expect(p.dispatch).toHaveBeenCalledWith(expect.any(Function));
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import * as React from "react";
|
||||
import { AddButtonProps } from "./interfaces";
|
||||
|
||||
export function AddButton({ active, click }: AddButtonProps) {
|
||||
export function AddButton({ active, onClick: click }: AddButtonProps) {
|
||||
if (!active) { return <div />; }
|
||||
return <button
|
||||
className="fb-button green add"
|
||||
|
|
|
@ -70,7 +70,7 @@ export class BulkScheduler extends React.Component<BulkEditorProps, {}> {
|
|||
return <div className="bulk-scheduler-content">
|
||||
<AddButton
|
||||
active={active}
|
||||
click={() => dispatch(commitBulkEditor())} />
|
||||
onClick={() => dispatch(commitBulkEditor())} />
|
||||
<Row>
|
||||
<this.SequenceSelectBox />
|
||||
<this.TimeSelection />
|
||||
|
|
|
@ -46,7 +46,7 @@ export interface ToggleDayParams {
|
|||
|
||||
export interface AddButtonProps {
|
||||
active: boolean;
|
||||
click: React.EventHandler<React.FormEvent<{}>>;
|
||||
onClick: React.EventHandler<React.FormEvent<{}>>;
|
||||
}
|
||||
|
||||
export interface SequenceListProps {
|
||||
|
|
|
@ -39,6 +39,7 @@ describe("<LocalsList/>", () => {
|
|||
onChange: jest.fn(),
|
||||
shouldDisplay: jest.fn(),
|
||||
allowedVariableNodes: AllowedVariableNodes.parameter,
|
||||
customFilterRule: undefined
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ describe("<LocationForm/>", () => {
|
|||
onChange: jest.fn(),
|
||||
shouldDisplay: jest.fn(),
|
||||
allowedVariableNodes: AllowedVariableNodes.parameter,
|
||||
customFilterRule: undefined
|
||||
});
|
||||
|
||||
it("renders correct UI components", () => {
|
||||
|
@ -44,7 +45,7 @@ describe("<LocationForm/>", () => {
|
|||
|
||||
expect(selects.length).toBe(1);
|
||||
const select = selects.first().props();
|
||||
const choices = locationFormList(p.resources, [PARENT("")], true);
|
||||
const choices = locationFormList(p.resources, [PARENT("")]);
|
||||
const actualLabels = select.list.map(x => x.label).sort();
|
||||
const expectedLabels = choices.map(x => x.label).sort();
|
||||
const diff = difference(actualLabels, expectedLabels);
|
||||
|
@ -116,7 +117,6 @@ describe("<LocationForm/>", () => {
|
|||
it("shows groups in dropdown", () => {
|
||||
const p = fakeProps();
|
||||
p.shouldDisplay = () => true;
|
||||
p.hideGroups = false;
|
||||
const wrapper = shallow(<LocationForm {...p} />);
|
||||
expect(wrapper.find(FBSelect).first().props().list).toContainEqual({
|
||||
headingId: "Coordinate",
|
||||
|
|
|
@ -6,7 +6,7 @@ import { LocationForm } from "./location_form";
|
|||
import {
|
||||
SequenceMeta, determineVector, determineDropdown
|
||||
} from "../../resources/sequence_meta";
|
||||
import { Help } from "../../ui";
|
||||
import { Help, DropDownItem } from "../../ui";
|
||||
import { ToolTips } from "../../constants";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { Position } from "@blueprintjs/core";
|
||||
|
@ -17,6 +17,9 @@ export interface DefaultValueFormProps {
|
|||
onChange: (v: ParameterDeclaration) => void;
|
||||
}
|
||||
|
||||
export const NO_GROUPS =
|
||||
(d: DropDownItem) => (d.headingId != "PointGroup");
|
||||
|
||||
export const DefaultValueForm = (props: DefaultValueFormProps) => {
|
||||
if (props.variableNode.kind === "parameter_declaration") {
|
||||
return <div className="default-value-form">
|
||||
|
@ -32,8 +35,8 @@ export const DefaultValueForm = (props: DefaultValueFormProps) => {
|
|||
shouldDisplay={() => true}
|
||||
allowedVariableNodes={AllowedVariableNodes.variable}
|
||||
hideTypeLabel={true}
|
||||
hideGroups={true}
|
||||
onChange={change(props.onChange, props.variableNode)} />
|
||||
onChange={change(props.onChange, props.variableNode)}
|
||||
customFilterRule={NO_GROUPS} />
|
||||
</div>;
|
||||
} else {
|
||||
return <div />;
|
||||
|
|
|
@ -46,21 +46,21 @@ export const LocalsList = (props: LocalsListProps) => {
|
|||
// Show default values for parameters as a fallback if not in Sequence header
|
||||
.map(v => v && props.bodyVariables && isParameterDeclaration(v.celeryNode)
|
||||
? convertFormVariable(v, props.resources) : v))
|
||||
.map(variable =>
|
||||
<LocationForm
|
||||
key={variable.celeryNode.args.label}
|
||||
locationDropdownKey={props.locationDropdownKey}
|
||||
bodyVariables={props.bodyVariables}
|
||||
variable={variable}
|
||||
sequenceUuid={props.sequenceUuid}
|
||||
resources={props.resources}
|
||||
shouldDisplay={props.shouldDisplay}
|
||||
hideVariableLabel={Object.values(props.variableData || {}).length < 2}
|
||||
allowedVariableNodes={props.allowedVariableNodes}
|
||||
collapsible={props.collapsible}
|
||||
collapsed={props.collapsed}
|
||||
toggleVarShow={props.toggleVarShow}
|
||||
onChange={props.onChange} />)}
|
||||
.map(variable => <LocationForm
|
||||
key={variable.celeryNode.args.label}
|
||||
locationDropdownKey={props.locationDropdownKey}
|
||||
bodyVariables={props.bodyVariables}
|
||||
variable={variable}
|
||||
sequenceUuid={props.sequenceUuid}
|
||||
resources={props.resources}
|
||||
shouldDisplay={props.shouldDisplay}
|
||||
hideVariableLabel={Object.values(props.variableData || {}).length < 2}
|
||||
allowedVariableNodes={props.allowedVariableNodes}
|
||||
collapsible={props.collapsible}
|
||||
collapsed={props.collapsed}
|
||||
toggleVarShow={props.toggleVarShow}
|
||||
onChange={props.onChange}
|
||||
customFilterRule={props.customFilterRule} />)}
|
||||
</div>;
|
||||
};
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
} from "../../resources/interfaces";
|
||||
import { SequenceMeta } from "../../resources/sequence_meta";
|
||||
import { ShouldDisplay } from "../../devices/interfaces";
|
||||
import { DropDownItem } from "../../ui";
|
||||
|
||||
export type VariableNode =
|
||||
ParameterDeclaration | VariableDeclaration | ParameterApplication;
|
||||
|
@ -49,13 +50,13 @@ interface CommonProps {
|
|||
* chooses between reassignment vs. creation for new variables,
|
||||
* and determines which variables to display in the form. */
|
||||
allowedVariableNodes: AllowedVariableNodes;
|
||||
/** Do not show `groups` as an option. Eg: Don't allow the user to pick
|
||||
* "group123" in the sequence editor header. */
|
||||
hideGroups?: boolean;
|
||||
/** Add ability to collapse the form content. */
|
||||
collapsible?: boolean;
|
||||
collapsed?: boolean;
|
||||
toggleVarShow?: () => void;
|
||||
/** Optional filter to allow removal of arbitrary dropdown items.
|
||||
* Return `false` to omit an item from display. */
|
||||
customFilterRule?: (ddi: DropDownItem) => boolean;
|
||||
}
|
||||
|
||||
export interface LocalsListProps extends CommonProps {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import * as React from "react";
|
||||
import { Row, Col, FBSelect, DropDownItem } from "../../ui";
|
||||
import { Row, Col, FBSelect } from "../../ui";
|
||||
import { locationFormList, NO_VALUE_SELECTED_DDI } from "./location_form_list";
|
||||
import { convertDDItoVariable } from "../locals_list/handle_select";
|
||||
import {
|
||||
|
@ -38,8 +38,6 @@ const maybeUseStepData = ({ resources, bodyVariables, variable, uuid }: {
|
|||
return variable;
|
||||
};
|
||||
|
||||
const hideGroups = (x: DropDownItem) => x.headingId !== "PointGroup";
|
||||
const allowAll = (_: unknown) => true;
|
||||
/**
|
||||
* Form with an "import from" dropdown and coordinate input boxes.
|
||||
* Can be used to set a specific value, import a value, or declare a variable.
|
||||
|
@ -57,8 +55,9 @@ export const LocationForm =
|
|||
const variableListItems = displayVariables ? [PARENT(determineVarDDILabel({
|
||||
label: "parent", resources, uuid: sequenceUuid, forceExternal: headerForm
|
||||
}))] : [];
|
||||
const list = locationFormList(resources, variableListItems)
|
||||
.filter(props.hideGroups ? hideGroups : allowAll);
|
||||
const unfiltered = locationFormList(resources, variableListItems);
|
||||
const list = props.customFilterRule ?
|
||||
unfiltered.filter(props.customFilterRule) : unfiltered;
|
||||
/** Variable name. */
|
||||
const { label } = celeryNode.args;
|
||||
if (variable.default) {
|
||||
|
|
|
@ -66,27 +66,22 @@ export const groups2Ddi = (groups: TaggedPointGroup[]): DropDownItem[] => {
|
|||
|
||||
/** Location selection menu items. */
|
||||
export function locationFormList(resources: ResourceIndex,
|
||||
additionalItems: DropDownItem[], displayGroups?: boolean): DropDownItem[] {
|
||||
additionalItems: DropDownItem[]): DropDownItem[] {
|
||||
const points = selectAllActivePoints(resources)
|
||||
.filter(x => x.body.pointer_type !== "ToolSlot");
|
||||
const plantDDI = points2ddi(points, "Plant");
|
||||
const genericPointerDDI = points2ddi(points, "GenericPointer");
|
||||
const toolDDI = activeToolDDIs(resources);
|
||||
const output = [COORDINATE_DDI()]
|
||||
return [COORDINATE_DDI()]
|
||||
.concat(additionalItems)
|
||||
.concat(heading("Tool"))
|
||||
.concat(toolDDI)
|
||||
.concat(heading("Plant"))
|
||||
.concat(plantDDI)
|
||||
.concat(heading("GenericPointer"))
|
||||
.concat(genericPointerDDI);
|
||||
if (displayGroups) {
|
||||
return output
|
||||
.concat(heading("PointGroup"))
|
||||
.concat(groups2Ddi(selectAllPointGroups(resources)));
|
||||
} else {
|
||||
return output;
|
||||
}
|
||||
.concat(genericPointerDDI)
|
||||
.concat(heading("PointGroup"))
|
||||
.concat(groups2Ddi(selectAllPointGroups(resources)));
|
||||
}
|
||||
|
||||
/** Create drop down item with label; i.e., "Point/Plant (1, 2, 3)" */
|
||||
|
|
|
@ -29,6 +29,7 @@ import {
|
|||
import { BooleanSetting } from "../session_keys";
|
||||
import { BooleanConfigKey } from "farmbot/dist/resources/configs/web_app";
|
||||
import { isUndefined } from "lodash";
|
||||
import { NO_GROUPS } from "./locals_list/default_value_form";
|
||||
|
||||
export const onDrop =
|
||||
(dispatch1: Function, sequence: TaggedSequence) =>
|
||||
|
@ -210,7 +211,8 @@ const SequenceHeader = (props: SequenceHeaderProps) => {
|
|||
collapsible={true}
|
||||
collapsed={props.variablesCollapsed}
|
||||
toggleVarShow={props.toggleVarShow}
|
||||
shouldDisplay={props.shouldDisplay} />
|
||||
shouldDisplay={props.shouldDisplay}
|
||||
customFilterRule={NO_GROUPS} />
|
||||
</div>;
|
||||
};
|
||||
|
||||
|
|
|
@ -118,8 +118,7 @@ export class RefactoredExecuteBlock
|
|||
onChange={assignVariable(this.props)(currentStep.body || [])}
|
||||
locationDropdownKey={JSON.stringify(currentSequence)}
|
||||
allowedVariableNodes={AllowedVariableNodes.identifier}
|
||||
shouldDisplay={this.props.shouldDisplay}
|
||||
hideGroups={false} />
|
||||
shouldDisplay={this.props.shouldDisplay} />
|
||||
</Col>}
|
||||
</Row>
|
||||
</StepContent>
|
||||
|
|
|
@ -22,6 +22,7 @@ import { MoveAbsoluteWarning } from "./tile_move_absolute_conflict_check";
|
|||
import { t } from "../../i18next_wrapper";
|
||||
import { Collapse } from "@blueprintjs/core";
|
||||
import { ExpandableHeader } from "../../ui/expandable_header";
|
||||
import { NO_GROUPS } from "../locals_list/default_value_form";
|
||||
|
||||
export class TileMoveAbsolute extends React.Component<StepParams, MoveAbsState> {
|
||||
state: MoveAbsState = {
|
||||
|
@ -96,8 +97,8 @@ export class TileMoveAbsolute extends React.Component<StepParams, MoveAbsState>
|
|||
hideHeader={true}
|
||||
locationDropdownKey={JSON.stringify(this.props.currentSequence)}
|
||||
allowedVariableNodes={AllowedVariableNodes.identifier}
|
||||
hideGroups={true}
|
||||
width={3} />
|
||||
width={3}
|
||||
customFilterRule={NO_GROUPS} />
|
||||
|
||||
SpeedInput = () =>
|
||||
<Col xs={3}>
|
||||
|
|
|
@ -29,6 +29,7 @@ describe Api::PointGroupsController do
|
|||
new_point_ids = rando_points + dont_delete
|
||||
payload = { name: "new name",
|
||||
point_ids: new_point_ids }
|
||||
Transport.current.connection.clear!
|
||||
put :update, body: payload.to_json, format: :json, params: { id: pg.id }
|
||||
expect(response.status).to eq(200)
|
||||
expect(PointGroupItem.exists?(do_delete)).to be false
|
||||
|
@ -36,5 +37,11 @@ describe Api::PointGroupsController do
|
|||
expect(json[:point_ids].count).to eq(new_point_ids.count)
|
||||
expect(json.fetch(:name)).to eq "new name"
|
||||
expect(new_point_ids.to_set).to eq(json.fetch(:point_ids).to_set)
|
||||
calls = Transport.current.connection.calls.fetch(:publish)
|
||||
expect(calls.length).to eq(1) # Don't echo!
|
||||
call1 = calls.first
|
||||
expect(call1.last.fetch(:routing_key)).to include(".sync.PointGroup.")
|
||||
json2 = JSON.parse(call1.first, symbolize_names: true).fetch(:body)
|
||||
expect(json).to eq(json2)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,11 +15,9 @@ describe User do
|
|||
|
||||
describe ".refresh_everyones_ui" do
|
||||
it "Sends a message over AMQP" do
|
||||
expect(Rollbar).to receive(:error).with("Global UI refresh triggered")
|
||||
get_msg = receive(:raw_amqp_send)
|
||||
.with({
|
||||
"type" => "reload", "commit" => "NONE",
|
||||
}.to_json, Api::RmqUtilsController::PUBLIC_BROADCAST)
|
||||
get_msg = receive(:raw_amqp_send).with({
|
||||
"type" => "reload", "commit" => "NONE",
|
||||
}.to_json, Api::RmqUtilsController::PUBLIC_BROADCAST)
|
||||
expect(Transport.current).to get_msg
|
||||
User.refresh_everyones_ui
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue