Show data consistency state via sync button

pull/529/head
Rick Carlino 2017-11-13 11:52:32 -06:00
parent 4496cf3040
commit 356b745d6e
9 changed files with 64 additions and 34 deletions

View File

@ -19,7 +19,8 @@ describe("<App />: Controls Pop-Up", () => {
loaded: [],
logs: [],
user: fakeUser(),
bot: bot
bot: bot,
consistent: true
};
}
@ -57,7 +58,8 @@ describe.skip("<App />: Loading", () => {
loaded: [],
logs: [],
user: fakeUser(),
bot: bot
bot: bot,
consistent: true
};
}
@ -88,7 +90,8 @@ describe("<App />: NavBar", () => {
loaded: [],
logs: [],
user: fakeUser(),
bot: bot
bot: bot,
consistent: true
};
}

View File

@ -27,6 +27,7 @@ export interface AppProps {
logs: Log[];
user: TaggedUser | undefined;
bot: BotState;
consistent: boolean;
}
function mapStateToProps(props: Everything): AppProps {
@ -39,7 +40,8 @@ function mapStateToProps(props: Everything): AppProps {
.sortBy("created_at")
.reverse()
.value(),
loaded: props.resources.loaded
loaded: props.resources.loaded,
consistent: props.connectivity.consistent
};
}
/** Time at which the app gives up and asks the user to refresh */
@ -82,6 +84,7 @@ export class App extends React.Component<AppProps, {}> {
return <div className="app">
<HotKeys dispatch={this.props.dispatch} />
<NavBar
consistent={this.props.consistent}
user={this.props.user}
bot={this.props.bot}
dispatch={this.props.dispatch}

View File

@ -1,18 +1,9 @@
import { AxiosRequestConfig } from "axios";
import { get, set } from "lodash";
import { uuid } from "farmbot";
import { store } from "../redux/store";
import { Actions } from "../constants";
const WHERE_WE_STORE_REQUEST_ID = "__FARMBOT_REQUEST_ID__";
function getRequestId(conf: AxiosRequestConfig): string {
return get(conf, WHERE_WE_STORE_REQUEST_ID);
}
export function setRequestId(conf: AxiosRequestConfig): string {
const u = uuid();
set(conf, [WHERE_WE_STORE_REQUEST_ID], u);
return u;
}
const outstandingRequests: Set<AxiosRequestConfig> = new Set();
(window as any)["outstanding_requests"] = outstandingRequests;
/**
* PROBLEM: You save a sequence and click "RUN" very fast. The remote device
@ -45,19 +36,27 @@ export function setRequestId(conf: AxiosRequestConfig): string {
* and friends.
*/
export function startTracking(conf: AxiosRequestConfig) {
const id = setRequestId(conf);
if (id) {
console.log(`START TRACKING ${conf.url}`);
} else {
throw new Error("NO ID");
}
outstandingRequests.add(conf);
toggleConsistency(false);
setTimeout(() => {
stopTracking(conf);
}, 3000);
}
export function stopTracking(conf: AxiosRequestConfig) {
const id = getRequestId(conf);
if (id) {
console.log(`STOP TRACKING ${conf.url}`);
} else {
throw new Error("NO ID");
outstandingRequests.delete(conf);
if (outstandingRequests.size < 1) {
toggleConsistency(true);
}
}
export const toggleConsistency = (payload: boolean) => {
const current = store.getState().connectivity.consistent === payload;
const state = payload ? "consistent" : "inconsistent";
if (current) {
console.log(`Already ${state}`);
} else {
console.log(`Flipping to ${state} state`);
store.dispatch({ type: Actions.SET_CONSISTENCY, payload });
}
};

View File

@ -24,9 +24,15 @@ export interface ResourceReady {
data: [DeviceAccountSettings];
}
type StatusRecord = Record<Edge, ConnectionStatus | undefined>;
/** Mapping of known connection status.
* An `undefined` value means we don't know. */
export type ConnectionState = Record<Edge, ConnectionStatus | undefined>;
export interface ConnectionState extends StatusRecord {
/** Have all API requests been acknowledged by external services?
* This flag lets us know if it is safe to do data critical tasks with the bot
*/
consistent: boolean;
}
export interface UpdateMqttData {
status: "UPDATE"

View File

@ -7,10 +7,15 @@ export const DEFAULT_STATE: ConnectionState = {
"bot.mqtt": undefined,
"user.mqtt": undefined,
"user.api": undefined,
consistent: true
};
export let connectivityReducer =
generateReducer<ConnectionState>(DEFAULT_STATE)
.add<boolean>(Actions.SET_CONSISTENCY, (s, { payload }) => {
s.consistent = payload;
return s;
})
.add<EdgeStatus>(Actions.NETWORK_EDGE_CHANGE, (s, { payload }) => {
s[payload.name] = payload.status;
return s;

View File

@ -441,5 +441,6 @@ export enum Actions {
// Network
NETWORK_EDGE_CHANGE = "NETWORK_EDGE_CHANGE",
RESET_NETWORK = "RESET_NETWORK"
RESET_NETWORK = "RESET_NETWORK",
SET_CONSISTENCY = "SET_CONSISTENCY"
}

View File

@ -11,6 +11,7 @@ describe("NavBar", () => {
it("has correct parent classname", () => {
const wrapper = shallow(
<NavBar
consistent={true}
logs={[log]}
bot={bot}
user={taggedUser}
@ -22,6 +23,7 @@ describe("NavBar", () => {
it("closes nav menu", () => {
const wrapper = shallow(<NavBar
consistent={true}
logs={[log]}
bot={bot}
user={taggedUser}

View File

@ -33,6 +33,19 @@ export class NavBar extends React.Component<NavBarProps, Partial<NavBarState>> {
close = (name: keyof NavBarState) => () =>
this.setState({ [name]: false });
syncButton = () => {
if (this.props.consistent) {
return <SyncButton
bot={this.props.bot}
user={this.props.user}
dispatch={this.props.dispatch} />;
} else {
return <button className="nav-sync red fb-button"
onClick={() => { }}>
{t("Saving...")}
</button>;
}
}
render() {
const hasName = this.props.user && this.props.user.body.name;
@ -88,10 +101,7 @@ export class NavBar extends React.Component<NavBarProps, Partial<NavBarState>> {
<EStopButton
bot={this.props.bot}
user={this.props.user} />
<SyncButton
bot={this.props.bot}
user={this.props.user}
dispatch={this.props.dispatch} />
{this.syncButton()}
</div>
</div>
</div>

View File

@ -10,6 +10,7 @@ export interface NavButtonProps {
}
export interface NavBarProps {
consistent: boolean;
logs: Log[];
bot: BotState;
user: TaggedUser | undefined;