misc bug fixes

pull/1060/head
gabrielburnworth 2018-12-03 13:36:43 -08:00
parent 9d8ffe46ee
commit 2bcb249e7a
20 changed files with 98 additions and 58 deletions

View File

@ -23,7 +23,7 @@ class GlobalConfig < ApplicationRecord
{ {
"NODE_ENV" => Rails.env || "development", "NODE_ENV" => Rails.env || "development",
"LONG_REVISION" => LONG_REVISION, "LONG_REVISION" => LONG_REVISION,
"SHORT_REVISION" => LONG_REVISION.first(7), "SHORT_REVISION" => LONG_REVISION.first(8),
}.map do |(key, value)| }.map do |(key, value)|
self.find_or_create_by(key: key).update_attributes(key: key, value: value) self.find_or_create_by(key: key).update_attributes(key: key, value: value)
end end

View File

@ -1,6 +1,7 @@
# A human # A human
class User < ApplicationRecord class User < ApplicationRecord
class AlreadyVerified < StandardError; end class AlreadyVerified < StandardError; end
# TODO: use GlobalConfig instead of ENV
ENFORCE_TOS = ENV.fetch("TOS_URL") { false } ENFORCE_TOS = ENV.fetch("TOS_URL") { false }
SKIP_EMAIL_VALIDATION = ENV.fetch("NO_EMAILS") { false } SKIP_EMAIL_VALIDATION = ENV.fetch("NO_EMAILS") { false }
validates :email, uniqueness: true validates :email, uniqueness: true

View File

@ -58,8 +58,8 @@ namespace :coverage do
puts "=" * 37 puts "=" * 37
puts "COVERAGE RESULTS" puts "COVERAGE RESULTS"
puts "This build: #{build_percent.round(8)}% #{CURRENT_COMMIT[0,7]}" puts "This build: #{build_percent.round(8)}% #{CURRENT_COMMIT[0,8]}"
puts "Staging build: #{staging_percent.round(8)}% #{latest_commit_staging[0,7]}" puts "Staging build: #{staging_percent.round(8)}% #{latest_commit_staging[0,8]}"
puts "=" * 37 puts "=" * 37
puts "Difference: #{diff.round(8)}%" puts "Difference: #{diff.round(8)}%"
puts "Pass?: #{pass ? "yes" : "no"}" puts "Pass?: #{pass ? "yes" : "no"}"

View File

@ -94,6 +94,18 @@ var HelperNamespace = (function () {
console.dir(getAllTags()); console.dir(getAllTags());
} }
/** For debugging. Replace all translations with a debug string. */
function replaceWithDebugString(key, debugString, debugStringOption) {
const debugChar = debugString[0];
switch (debugStringOption) {
case 'r': return debugString; // replace with: string as provided
case 's': return debugChar; // single character
case 'n': return key.replace(/\S/g, debugChar); // maintain whitespace
case 'l': return debugChar.repeat(key.length) // replace whitespace
default: return key;
}
}
/** /**
* Label a section of tags with a comment before the first tag in the section. * Label a section of tags with a comment before the first tag in the section.
*/ */
@ -189,6 +201,7 @@ var HelperNamespace = (function () {
// For debugging // For debugging
const debug = process.argv[3]; const debug = process.argv[3];
const debugOption = process.argv[4];
// merge new tags with existing translation // merge new tags with existing translation
var result = {}; var result = {};
@ -198,14 +211,18 @@ var HelperNamespace = (function () {
// all current tags in English // all current tags in English
Object.keys(jsonCurrentTagData).sort(localeSort).map(function (key) { Object.keys(jsonCurrentTagData).sort(localeSort).map(function (key) {
result[key] = jsonCurrentTagData[key]; result[key] = jsonCurrentTagData[key];
if (debug) { result[key] = debug[0].repeat(key.length) } if (debug) {
result[key] = replaceWithDebugString(key, debug, debugOption);
}
}) })
for (var key in ordered) { for (var key in ordered) {
// replace current tag with an existing translation // replace current tag with an existing translation
if (result.hasOwnProperty(key)) { if (result.hasOwnProperty(key)) {
delete result[key]; delete result[key];
result[key] = ordered[key]; result[key] = ordered[key];
if (debug) { result[key] = debug[0].repeat(key.length) } if (debug) {
result[key] = replaceWithDebugString(key, debug, debugOption);
}
existing++; existing++;
if (key !== result[key]) { translated++; } if (key !== result[key]) { translated++; }
} }

View File

@ -126,6 +126,7 @@
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
height: 2rem; height: 2rem;
padding: 0.2rem;
} }
.right-button { .right-button {
float: right; float: right;

View File

@ -41,6 +41,8 @@ input:not([role="combobox"]) {
.week-row { .week-row {
height: 30.5px; height: 30.5px;
width: 108%;
margin-left: -1rem;
} }
select { select {

View File

@ -111,6 +111,11 @@ nav {
label { label {
color: $white; color: $white;
} }
p {
display: inline;
color: $gray;
font-size: 1.2rem;
}
} }
} }
.bp3-overlay-content { .bp3-overlay-content {

View File

@ -5,7 +5,9 @@
} }
.week-grid-meta-buttons { .week-grid-meta-buttons {
margin-top: 1rem; margin-top: 1rem;
text-align: right;
button { button {
float: none;
margin-left: 1rem; margin-left: 1rem;
margin-bottom: 1rem; margin-bottom: 1rem;
} }

View File

@ -74,9 +74,8 @@
margin-left: 1rem; margin-left: 1rem;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
} }
} img {
width: 55%;
img { margin-bottom: 2rem;
width: 55%; }
margin-bottom: 2rem;
} }

View File

@ -18,17 +18,21 @@ describe("executableType", () => {
}); });
describe("OFSearch()", () => { describe("OFSearch()", () => {
const START = expect.objectContaining({
type: Actions.OF_SEARCH_RESULTS_START
});
const NO = expect.objectContaining({ type: Actions.OF_SEARCH_RESULTS_NO });
it("searches: no image", async () => { it("searches: no image", async () => {
mockPromise = Promise.resolve({ data: { data: [{ attributes: {} }] } }); mockPromise = Promise.resolve({ data: { data: [{ attributes: {} }] } });
const dispatch = jest.fn(); const dispatch = jest.fn();
await OFSearch("mint")(dispatch); await OFSearch("mint")(dispatch);
expect(dispatch).toHaveBeenCalledWith(START);
await expect(dispatch).toHaveBeenCalledWith({ await expect(dispatch).toHaveBeenCalledWith({
type: Actions.OF_SEARCH_RESULTS_OK, payload: [ type: Actions.OF_SEARCH_RESULTS_OK, payload: [
{ crop: {}, image: "/app-resources/img/generic-plant.svg" }] { crop: {}, image: "/app-resources/img/generic-plant.svg" }]
}); });
await expect(dispatch).not.toHaveBeenCalledWith(expect.objectContaining({ await expect(dispatch).not.toHaveBeenCalledWith(NO);
type: Actions.OF_SEARCH_RESULTS_NO
}));
}); });
it("searches: image", async () => { it("searches: image", async () => {
@ -43,24 +47,22 @@ describe("OFSearch()", () => {
}); });
const dispatch = jest.fn(); const dispatch = jest.fn();
await OFSearch("mint")(dispatch); await OFSearch("mint")(dispatch);
expect(dispatch).toHaveBeenCalledWith(START);
await expect(dispatch).toHaveBeenCalledWith({ await expect(dispatch).toHaveBeenCalledWith({
type: Actions.OF_SEARCH_RESULTS_OK, payload: [ type: Actions.OF_SEARCH_RESULTS_OK, payload: [
{ crop: {}, image: "thumbnail_url" }] { crop: {}, image: "thumbnail_url" }]
}); });
await expect(dispatch).not.toHaveBeenCalledWith(expect.objectContaining({ await expect(dispatch).not.toHaveBeenCalledWith(NO);
type: Actions.OF_SEARCH_RESULTS_NO
}));
}); });
it("fails search", async () => { it("fails search", async () => {
mockPromise = Promise.reject(); mockPromise = Promise.reject();
const dispatch = jest.fn(); const dispatch = jest.fn();
await OFSearch("mint")(dispatch); await OFSearch("mint")(dispatch);
expect(dispatch).toHaveBeenCalledWith(START);
await expect(dispatch).not.toHaveBeenCalledWith(expect.objectContaining({ await expect(dispatch).not.toHaveBeenCalledWith(expect.objectContaining({
type: Actions.OF_SEARCH_RESULTS_OK type: Actions.OF_SEARCH_RESULTS_OK
})); }));
await expect(dispatch).toHaveBeenCalledWith({ await expect(dispatch).toHaveBeenCalledWith(NO);
type: Actions.OF_SEARCH_RESULTS_NO, payload: undefined
});
}); });
}); });

View File

@ -27,7 +27,6 @@ import {
fakeCropLiveSearchResult fakeCropLiveSearchResult
} from "../../../__test_support__/fake_crop_search_result"; } from "../../../__test_support__/fake_crop_search_result";
import { unselectPlant } from "../../actions"; import { unselectPlant } from "../../actions";
import { Actions } from "../../../constants";
describe("<CropInfo />", () => { describe("<CropInfo />", () => {
const fakeProps = (): CropInfoProps => { const fakeProps = (): CropInfoProps => {
@ -118,8 +117,5 @@ describe("searchForCurrentCrop()", () => {
searchForCurrentCrop(fakeOFSearch)(dispatch); searchForCurrentCrop(fakeOFSearch)(dispatch);
expect(fakeOFSearch).toHaveBeenCalledWith("mint"); expect(fakeOFSearch).toHaveBeenCalledWith("mint");
expect(unselectPlant).toHaveBeenCalled(); expect(unselectPlant).toHaveBeenCalled();
expect(dispatch).toHaveBeenCalledWith(expect.objectContaining({
type: Actions.OF_SEARCH_RESULTS_START
}));
}); });
}); });

View File

@ -55,6 +55,10 @@ export class CropCatalog extends React.Component<CropCatalogProps, {}> {
this.validSearchTerm; this.validSearchTerm;
} }
componentDidMount() {
this.props.openfarmSearch(this.props.cropSearchQuery)(this.props.dispatch);
}
render() { render() {
return <DesignerPanel panelName={"crop-catalog"} panelColor={"green"}> return <DesignerPanel panelName={"crop-catalog"} panelColor={"green"}>
<DesignerPanelHeader <DesignerPanelHeader

View File

@ -209,12 +209,15 @@ export function mapStateToProps(props: Everything): CropInfoProps {
/** Get OpenFarm crop search results for crop info page contents. */ /** Get OpenFarm crop search results for crop info page contents. */
export const searchForCurrentCrop = (openfarmSearch: OpenfarmSearch) => export const searchForCurrentCrop = (openfarmSearch: OpenfarmSearch) =>
(dispatch: Function) => { (dispatch: Function) => {
dispatch({ type: Actions.OF_SEARCH_RESULTS_START, payload: true });
const crop = getPathArray()[5]; const crop = getPathArray()[5];
openfarmSearch(crop)(dispatch); openfarmSearch(crop)(dispatch);
unselectPlant(dispatch)(); unselectPlant(dispatch)();
}; };
/** Clear the current crop search results. */
const clearCropSearchResults = (dispatch: Function) => () =>
dispatch({ type: Actions.OF_SEARCH_RESULTS_OK, payload: [] });
@connect(mapStateToProps) @connect(mapStateToProps)
export class CropInfo extends React.Component<CropInfoProps, {}> { export class CropInfo extends React.Component<CropInfoProps, {}> {
@ -233,6 +236,7 @@ export class CropInfo extends React.Component<CropInfoProps, {}> {
panelColor={"green"} panelColor={"green"}
title={result.crop.name} title={result.crop.name}
backTo={basePath} backTo={basePath}
onBack={clearCropSearchResults(this.props.dispatch)}
style={{ background: backgroundURL }} style={{ background: backgroundURL }}
description={result.crop.description}> description={result.crop.description}>
<AddToMapButton basePath={basePath} crop={crop} /> <AddToMapButton basePath={basePath} crop={crop} />

View File

@ -17,6 +17,7 @@ interface IdURL {
const FALLBACK: OpenFarm.Included[] = []; const FALLBACK: OpenFarm.Included[] = [];
export let OFSearch = (searchTerm: string) => export let OFSearch = (searchTerm: string) =>
(dispatch: Function) => { (dispatch: Function) => {
dispatch({ type: Actions.OF_SEARCH_RESULTS_START, payload: undefined });
openFarmSearchQuery(searchTerm) openFarmSearchQuery(searchTerm)
.then(resp => { .then(resp => {
const images: { [key: string]: string } = {}; const images: { [key: string]: string } = {};

View File

@ -42,9 +42,9 @@ describe("<SyncButton/>", function () {
it("defaults to `disconnected` and `red` when uncertain", () => { it("defaults to `disconnected` and `red` when uncertain", () => {
const p = fakeProps(); const p = fakeProps();
// tslint:disable-next-line:no-any // tslint:disable-next-line:no-any
p.bot.hardware.informational_settings.sync_status = "mistake" as any; p.bot.hardware.informational_settings.sync_status = "new" as any;
const result = shallow(<SyncButton {...p} />); const result = shallow(<SyncButton {...p} />);
expect(result.text()).toContain("DISCONNECTED"); expect(result.text()).toContain("new");
expect(result.hasClass("red")).toBeTruthy(); expect(result.hasClass("red")).toBeTruthy();
}); });

View File

@ -3,6 +3,7 @@ import { t } from "i18next";
import { AccountMenuProps } from "./interfaces"; import { AccountMenuProps } from "./interfaces";
import { docLink } from "../ui/doc_link"; import { docLink } from "../ui/doc_link";
import { Link } from "../link"; import { Link } from "../link";
import { shortRevision } from "../util";
export const AdditionalMenu = (props: AccountMenuProps) => { export const AdditionalMenu = (props: AccountMenuProps) => {
return <div className="nav-additional-menu"> return <div className="nav-additional-menu">
@ -34,7 +35,7 @@ export const AdditionalMenu = (props: AccountMenuProps) => {
<a <a
href="https://github.com/FarmBot/Farmbot-Web-App" href="https://github.com/FarmBot/Farmbot-Web-App"
target="_blank"> target="_blank">
{(globalConfig.SHORT_REVISION || "NONE").slice(0, 7)} {shortRevision().slice(0, 7)}<p>{shortRevision().slice(7, 8)}</p>
</a> </a>
</div> </div>
</div>; </div>;

View File

@ -14,7 +14,7 @@ const COLOR_MAPPING: Record<SyncStatus, string> = {
"unknown": "red" "unknown": "red"
}; };
const TEXT_MAPPING: Record<SyncStatus, string> = { const TEXT_MAPPING: () => Record<SyncStatus, string> = () => ({
"synced": t("SYNCED"), "synced": t("SYNCED"),
"sync_now": t("SYNC NOW"), "sync_now": t("SYNC NOW"),
"syncing": t("SYNCING"), "syncing": t("SYNCING"),
@ -22,7 +22,7 @@ const TEXT_MAPPING: Record<SyncStatus, string> = {
"booting": t("BOOTING"), "booting": t("BOOTING"),
"unknown": t("DISCONNECTED"), "unknown": t("DISCONNECTED"),
"maintenance": t("MAINTENANCE DOWNTIME") "maintenance": t("MAINTENANCE DOWNTIME")
}; });
/** Animation during syncing action */ /** Animation during syncing action */
const spinner = <span className="btn-spinner sync" />; const spinner = <span className="btn-spinner sync" />;
@ -30,15 +30,16 @@ const spinner = <span className="btn-spinner sync" />;
export function SyncButton({ user, bot, dispatch, consistent }: NavButtonProps) { export function SyncButton({ user, bot, dispatch, consistent }: NavButtonProps) {
if (!user) { if (!user) {
return <span></span>; return <span />;
} }
let { sync_status } = bot.hardware.informational_settings; const { sync_status } = bot.hardware.informational_settings;
sync_status = sync_status || "unknown"; const syncStatus = sync_status || "unknown";
const color = !consistent && sync_status === "sync_now" const normalColor = COLOR_MAPPING[syncStatus] || "red";
const color = (!consistent && (syncStatus === "sync_now"))
? "gray" ? "gray"
: (COLOR_MAPPING[sync_status] || "red"); : normalColor;
const text = TEXT_MAPPING[sync_status] || t("DISCONNECTED"); const text = TEXT_MAPPING()[syncStatus] || syncStatus.replace("_", " ");
const spinnerEl = (sync_status === "syncing") ? spinner : ""; const spinnerEl = (syncStatus === "syncing") ? spinner : "";
return <button return <button
className={`nav-sync ${color} fb-button`} className={`nav-sync ${color} fb-button`}

View File

@ -21,26 +21,30 @@ export function WeekGrid({ weeks, dispatch }: WeekGridProps) {
<Row> <Row>
<Col xs={12}> <Col xs={12}>
<div className="week-grid-meta-buttons"> <div className="week-grid-meta-buttons">
<button <div>
className="green widget-control fb-button" <button
onClick={() => dispatch(pushWeek())}> className="green widget-control fb-button"
<i className="fa fa-plus" /> {t("Week")} onClick={() => dispatch(pushWeek())}>
</button> <i className="fa fa-plus" /> {t("Week")}
<button </button>
className="red widget-control fb-button" <button
onClick={() => dispatch(popWeek())}> className="red widget-control fb-button"
<i className="fa fa-minus" /> {t("Week")} onClick={() => dispatch(popWeek())}>
</button> <i className="fa fa-minus" /> {t("Week")}
<button </button>
className="gray widget-control fb-button" </div>
onClick={() => dispatch(deselectDays())}> <div>
{t("Deselect all")} <button
</button> className="gray widget-control fb-button"
<button onClick={() => dispatch(deselectDays())}>
className="gray widget-control fb-button" {t("Deselect all")}
onClick={() => dispatch(selectDays())}> </button>
{t("Select all")} <button
</button> className="gray widget-control fb-button"
onClick={() => dispatch(selectDays())}>
{t("Select all")}
</button>
</div>
</div> </div>
</Col> </Col>
</Row> </Row>

View File

@ -12,7 +12,7 @@ interface CenterProps {
} }
export function CenterPanel(props: CenterProps) { export function CenterPanel(props: CenterProps) {
return <Col sm={props.width || 6}> return <Col sm={props.width || 6} lg={6}>
<div className={props.className}> <div className={props.className}>
<h3> <h3>
<i>{t(props.title)}</i> <i>{t(props.title)}</i>

View File

@ -12,7 +12,7 @@ interface RightPanelProps {
} }
export function RightPanel(props: RightPanelProps) { export function RightPanel(props: RightPanelProps) {
return <Col sm={props.width || 3}> return <Col sm={props.width || 3} lg={3}>
{props.show && {props.show &&
<div className={props.className}> <div className={props.className}>
<h3> <h3>