misc bug fixes
parent
9d8ffe46ee
commit
2bcb249e7a
|
@ -23,7 +23,7 @@ class GlobalConfig < ApplicationRecord
|
|||
{
|
||||
"NODE_ENV" => Rails.env || "development",
|
||||
"LONG_REVISION" => LONG_REVISION,
|
||||
"SHORT_REVISION" => LONG_REVISION.first(7),
|
||||
"SHORT_REVISION" => LONG_REVISION.first(8),
|
||||
}.map do |(key, value)|
|
||||
self.find_or_create_by(key: key).update_attributes(key: key, value: value)
|
||||
end
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# A human
|
||||
class User < ApplicationRecord
|
||||
class AlreadyVerified < StandardError; end
|
||||
# TODO: use GlobalConfig instead of ENV
|
||||
ENFORCE_TOS = ENV.fetch("TOS_URL") { false }
|
||||
SKIP_EMAIL_VALIDATION = ENV.fetch("NO_EMAILS") { false }
|
||||
validates :email, uniqueness: true
|
||||
|
|
|
@ -58,8 +58,8 @@ namespace :coverage do
|
|||
|
||||
puts "=" * 37
|
||||
puts "COVERAGE RESULTS"
|
||||
puts "This build: #{build_percent.round(8)}% #{CURRENT_COMMIT[0,7]}"
|
||||
puts "Staging build: #{staging_percent.round(8)}% #{latest_commit_staging[0,7]}"
|
||||
puts "This build: #{build_percent.round(8)}% #{CURRENT_COMMIT[0,8]}"
|
||||
puts "Staging build: #{staging_percent.round(8)}% #{latest_commit_staging[0,8]}"
|
||||
puts "=" * 37
|
||||
puts "Difference: #{diff.round(8)}%"
|
||||
puts "Pass?: #{pass ? "yes" : "no"}"
|
||||
|
|
|
@ -94,6 +94,18 @@ var HelperNamespace = (function () {
|
|||
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.
|
||||
*/
|
||||
|
@ -189,6 +201,7 @@ var HelperNamespace = (function () {
|
|||
|
||||
// For debugging
|
||||
const debug = process.argv[3];
|
||||
const debugOption = process.argv[4];
|
||||
|
||||
// merge new tags with existing translation
|
||||
var result = {};
|
||||
|
@ -198,14 +211,18 @@ var HelperNamespace = (function () {
|
|||
// all current tags in English
|
||||
Object.keys(jsonCurrentTagData).sort(localeSort).map(function (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) {
|
||||
// replace current tag with an existing translation
|
||||
if (result.hasOwnProperty(key)) {
|
||||
delete result[key];
|
||||
result[key] = ordered[key];
|
||||
if (debug) { result[key] = debug[0].repeat(key.length) }
|
||||
if (debug) {
|
||||
result[key] = replaceWithDebugString(key, debug, debugOption);
|
||||
}
|
||||
existing++;
|
||||
if (key !== result[key]) { translated++; }
|
||||
}
|
||||
|
|
|
@ -126,6 +126,7 @@
|
|||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
height: 2rem;
|
||||
padding: 0.2rem;
|
||||
}
|
||||
.right-button {
|
||||
float: right;
|
||||
|
|
|
@ -41,6 +41,8 @@ input:not([role="combobox"]) {
|
|||
|
||||
.week-row {
|
||||
height: 30.5px;
|
||||
width: 108%;
|
||||
margin-left: -1rem;
|
||||
}
|
||||
|
||||
select {
|
||||
|
|
|
@ -111,6 +111,11 @@ nav {
|
|||
label {
|
||||
color: $white;
|
||||
}
|
||||
p {
|
||||
display: inline;
|
||||
color: $gray;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
.bp3-overlay-content {
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
}
|
||||
.week-grid-meta-buttons {
|
||||
margin-top: 1rem;
|
||||
text-align: right;
|
||||
button {
|
||||
float: none;
|
||||
margin-left: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
|
|
@ -74,9 +74,8 @@
|
|||
margin-left: 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
width: 55%;
|
||||
margin-bottom: 2rem;
|
||||
img {
|
||||
width: 55%;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,17 +18,21 @@ describe("executableType", () => {
|
|||
});
|
||||
|
||||
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 () => {
|
||||
mockPromise = Promise.resolve({ data: { data: [{ attributes: {} }] } });
|
||||
const dispatch = jest.fn();
|
||||
await OFSearch("mint")(dispatch);
|
||||
expect(dispatch).toHaveBeenCalledWith(START);
|
||||
await expect(dispatch).toHaveBeenCalledWith({
|
||||
type: Actions.OF_SEARCH_RESULTS_OK, payload: [
|
||||
{ crop: {}, image: "/app-resources/img/generic-plant.svg" }]
|
||||
});
|
||||
await expect(dispatch).not.toHaveBeenCalledWith(expect.objectContaining({
|
||||
type: Actions.OF_SEARCH_RESULTS_NO
|
||||
}));
|
||||
await expect(dispatch).not.toHaveBeenCalledWith(NO);
|
||||
});
|
||||
|
||||
it("searches: image", async () => {
|
||||
|
@ -43,24 +47,22 @@ describe("OFSearch()", () => {
|
|||
});
|
||||
const dispatch = jest.fn();
|
||||
await OFSearch("mint")(dispatch);
|
||||
expect(dispatch).toHaveBeenCalledWith(START);
|
||||
await expect(dispatch).toHaveBeenCalledWith({
|
||||
type: Actions.OF_SEARCH_RESULTS_OK, payload: [
|
||||
{ crop: {}, image: "thumbnail_url" }]
|
||||
});
|
||||
await expect(dispatch).not.toHaveBeenCalledWith(expect.objectContaining({
|
||||
type: Actions.OF_SEARCH_RESULTS_NO
|
||||
}));
|
||||
await expect(dispatch).not.toHaveBeenCalledWith(NO);
|
||||
});
|
||||
|
||||
it("fails search", async () => {
|
||||
mockPromise = Promise.reject();
|
||||
const dispatch = jest.fn();
|
||||
await OFSearch("mint")(dispatch);
|
||||
expect(dispatch).toHaveBeenCalledWith(START);
|
||||
await expect(dispatch).not.toHaveBeenCalledWith(expect.objectContaining({
|
||||
type: Actions.OF_SEARCH_RESULTS_OK
|
||||
}));
|
||||
await expect(dispatch).toHaveBeenCalledWith({
|
||||
type: Actions.OF_SEARCH_RESULTS_NO, payload: undefined
|
||||
});
|
||||
await expect(dispatch).toHaveBeenCalledWith(NO);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -27,7 +27,6 @@ import {
|
|||
fakeCropLiveSearchResult
|
||||
} from "../../../__test_support__/fake_crop_search_result";
|
||||
import { unselectPlant } from "../../actions";
|
||||
import { Actions } from "../../../constants";
|
||||
|
||||
describe("<CropInfo />", () => {
|
||||
const fakeProps = (): CropInfoProps => {
|
||||
|
@ -118,8 +117,5 @@ describe("searchForCurrentCrop()", () => {
|
|||
searchForCurrentCrop(fakeOFSearch)(dispatch);
|
||||
expect(fakeOFSearch).toHaveBeenCalledWith("mint");
|
||||
expect(unselectPlant).toHaveBeenCalled();
|
||||
expect(dispatch).toHaveBeenCalledWith(expect.objectContaining({
|
||||
type: Actions.OF_SEARCH_RESULTS_START
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
|
|
@ -55,6 +55,10 @@ export class CropCatalog extends React.Component<CropCatalogProps, {}> {
|
|||
this.validSearchTerm;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.openfarmSearch(this.props.cropSearchQuery)(this.props.dispatch);
|
||||
}
|
||||
|
||||
render() {
|
||||
return <DesignerPanel panelName={"crop-catalog"} panelColor={"green"}>
|
||||
<DesignerPanelHeader
|
||||
|
|
|
@ -209,12 +209,15 @@ export function mapStateToProps(props: Everything): CropInfoProps {
|
|||
/** Get OpenFarm crop search results for crop info page contents. */
|
||||
export const searchForCurrentCrop = (openfarmSearch: OpenfarmSearch) =>
|
||||
(dispatch: Function) => {
|
||||
dispatch({ type: Actions.OF_SEARCH_RESULTS_START, payload: true });
|
||||
const crop = getPathArray()[5];
|
||||
openfarmSearch(crop)(dispatch);
|
||||
unselectPlant(dispatch)();
|
||||
};
|
||||
|
||||
/** Clear the current crop search results. */
|
||||
const clearCropSearchResults = (dispatch: Function) => () =>
|
||||
dispatch({ type: Actions.OF_SEARCH_RESULTS_OK, payload: [] });
|
||||
|
||||
@connect(mapStateToProps)
|
||||
export class CropInfo extends React.Component<CropInfoProps, {}> {
|
||||
|
||||
|
@ -233,6 +236,7 @@ export class CropInfo extends React.Component<CropInfoProps, {}> {
|
|||
panelColor={"green"}
|
||||
title={result.crop.name}
|
||||
backTo={basePath}
|
||||
onBack={clearCropSearchResults(this.props.dispatch)}
|
||||
style={{ background: backgroundURL }}
|
||||
description={result.crop.description}>
|
||||
<AddToMapButton basePath={basePath} crop={crop} />
|
||||
|
|
|
@ -17,6 +17,7 @@ interface IdURL {
|
|||
const FALLBACK: OpenFarm.Included[] = [];
|
||||
export let OFSearch = (searchTerm: string) =>
|
||||
(dispatch: Function) => {
|
||||
dispatch({ type: Actions.OF_SEARCH_RESULTS_START, payload: undefined });
|
||||
openFarmSearchQuery(searchTerm)
|
||||
.then(resp => {
|
||||
const images: { [key: string]: string } = {};
|
||||
|
|
|
@ -42,9 +42,9 @@ describe("<SyncButton/>", function () {
|
|||
it("defaults to `disconnected` and `red` when uncertain", () => {
|
||||
const p = fakeProps();
|
||||
// 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} />);
|
||||
expect(result.text()).toContain("DISCONNECTED");
|
||||
expect(result.text()).toContain("new");
|
||||
expect(result.hasClass("red")).toBeTruthy();
|
||||
});
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import { t } from "i18next";
|
|||
import { AccountMenuProps } from "./interfaces";
|
||||
import { docLink } from "../ui/doc_link";
|
||||
import { Link } from "../link";
|
||||
import { shortRevision } from "../util";
|
||||
|
||||
export const AdditionalMenu = (props: AccountMenuProps) => {
|
||||
return <div className="nav-additional-menu">
|
||||
|
@ -34,7 +35,7 @@ export const AdditionalMenu = (props: AccountMenuProps) => {
|
|||
<a
|
||||
href="https://github.com/FarmBot/Farmbot-Web-App"
|
||||
target="_blank">
|
||||
{(globalConfig.SHORT_REVISION || "NONE").slice(0, 7)}
|
||||
{shortRevision().slice(0, 7)}<p>{shortRevision().slice(7, 8)}</p>
|
||||
</a>
|
||||
</div>
|
||||
</div>;
|
||||
|
|
|
@ -14,7 +14,7 @@ const COLOR_MAPPING: Record<SyncStatus, string> = {
|
|||
"unknown": "red"
|
||||
};
|
||||
|
||||
const TEXT_MAPPING: Record<SyncStatus, string> = {
|
||||
const TEXT_MAPPING: () => Record<SyncStatus, string> = () => ({
|
||||
"synced": t("SYNCED"),
|
||||
"sync_now": t("SYNC NOW"),
|
||||
"syncing": t("SYNCING"),
|
||||
|
@ -22,7 +22,7 @@ const TEXT_MAPPING: Record<SyncStatus, string> = {
|
|||
"booting": t("BOOTING"),
|
||||
"unknown": t("DISCONNECTED"),
|
||||
"maintenance": t("MAINTENANCE DOWNTIME")
|
||||
};
|
||||
});
|
||||
|
||||
/** Animation during syncing action */
|
||||
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) {
|
||||
|
||||
if (!user) {
|
||||
return <span></span>;
|
||||
return <span />;
|
||||
}
|
||||
let { sync_status } = bot.hardware.informational_settings;
|
||||
sync_status = sync_status || "unknown";
|
||||
const color = !consistent && sync_status === "sync_now"
|
||||
const { sync_status } = bot.hardware.informational_settings;
|
||||
const syncStatus = sync_status || "unknown";
|
||||
const normalColor = COLOR_MAPPING[syncStatus] || "red";
|
||||
const color = (!consistent && (syncStatus === "sync_now"))
|
||||
? "gray"
|
||||
: (COLOR_MAPPING[sync_status] || "red");
|
||||
const text = TEXT_MAPPING[sync_status] || t("DISCONNECTED");
|
||||
const spinnerEl = (sync_status === "syncing") ? spinner : "";
|
||||
: normalColor;
|
||||
const text = TEXT_MAPPING()[syncStatus] || syncStatus.replace("_", " ");
|
||||
const spinnerEl = (syncStatus === "syncing") ? spinner : "";
|
||||
|
||||
return <button
|
||||
className={`nav-sync ${color} fb-button`}
|
||||
|
|
|
@ -21,26 +21,30 @@ export function WeekGrid({ weeks, dispatch }: WeekGridProps) {
|
|||
<Row>
|
||||
<Col xs={12}>
|
||||
<div className="week-grid-meta-buttons">
|
||||
<button
|
||||
className="green widget-control fb-button"
|
||||
onClick={() => dispatch(pushWeek())}>
|
||||
<i className="fa fa-plus" /> {t("Week")}
|
||||
</button>
|
||||
<button
|
||||
className="red widget-control fb-button"
|
||||
onClick={() => dispatch(popWeek())}>
|
||||
<i className="fa fa-minus" /> {t("Week")}
|
||||
</button>
|
||||
<button
|
||||
className="gray widget-control fb-button"
|
||||
onClick={() => dispatch(deselectDays())}>
|
||||
{t("Deselect all")}
|
||||
</button>
|
||||
<button
|
||||
className="gray widget-control fb-button"
|
||||
onClick={() => dispatch(selectDays())}>
|
||||
{t("Select all")}
|
||||
</button>
|
||||
<div>
|
||||
<button
|
||||
className="green widget-control fb-button"
|
||||
onClick={() => dispatch(pushWeek())}>
|
||||
<i className="fa fa-plus" /> {t("Week")}
|
||||
</button>
|
||||
<button
|
||||
className="red widget-control fb-button"
|
||||
onClick={() => dispatch(popWeek())}>
|
||||
<i className="fa fa-minus" /> {t("Week")}
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
className="gray widget-control fb-button"
|
||||
onClick={() => dispatch(deselectDays())}>
|
||||
{t("Deselect all")}
|
||||
</button>
|
||||
<button
|
||||
className="gray widget-control fb-button"
|
||||
onClick={() => dispatch(selectDays())}>
|
||||
{t("Select all")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
|
@ -12,7 +12,7 @@ interface CenterProps {
|
|||
}
|
||||
|
||||
export function CenterPanel(props: CenterProps) {
|
||||
return <Col sm={props.width || 6}>
|
||||
return <Col sm={props.width || 6} lg={6}>
|
||||
<div className={props.className}>
|
||||
<h3>
|
||||
<i>{t(props.title)}</i>
|
||||
|
|
|
@ -12,7 +12,7 @@ interface RightPanelProps {
|
|||
}
|
||||
|
||||
export function RightPanel(props: RightPanelProps) {
|
||||
return <Col sm={props.width || 3}>
|
||||
return <Col sm={props.width || 3} lg={3}>
|
||||
{props.show &&
|
||||
<div className={props.className}>
|
||||
<h3>
|
||||
|
|
Loading…
Reference in New Issue