add bulletin alert card UI

pull/1167/head
gabrielburnworth 2019-04-19 15:48:43 -07:00
parent d856a4781e
commit ce0c161b1f
11 changed files with 148 additions and 12 deletions

View File

@ -432,7 +432,7 @@ export function fakeFarmwareInstallation(): TaggedFarmwareInstallation {
export function fakeAlert(): TaggedAlert {
return fakeResource("Alert", {
uuid: "uuid",
slug: "slug",
created_at: 123,
problem_tag: "api.noun.verb",
priority: 100,

View File

@ -9,11 +9,13 @@ describe("API", () => {
API.setBaseUrl(BASE);
[
[API.current.pointSearchPath, BASE + "/api/points/search"],
[API.current.allPointsPath, BASE + "/api/points/?filter=all"],
[API.current.sensorReadingPath, BASE + "/api/sensor_readings"],
[API.current.farmwareEnvPath, BASE + "/api/farmware_envs/"],
[API.current.plantTemplatePath, BASE + "/api/plant_templates/"],
[API.current.diagnosticDumpsPath, BASE + "/api/diagnostic_dumps/"],
[API.current.farmwareInstallationPath, BASE + "/api/farmware_installations/"],
[API.current.globalBulletinPath, BASE + "/api/global_bulletins/"],
].map(x => expect(x[0]).toEqual(x[1]));
});

View File

@ -154,5 +154,7 @@ export class API {
}
/** /api/alerts/:id */
get alertPath() { return `${this.baseUrl}/api/alerts/`; }
/** /api/global_bulletins/ */
get globalBulletinPath() { return `${this.baseUrl}/api/global_bulletins/`; }
get syncPatch() { return `${this.baseUrl}/api/device/sync/`; }
}

View File

@ -0,0 +1,15 @@
jest.mock("axios", () => ({
get: jest.fn(() => Promise.resolve({ data: { foo: "bar" } })),
}));
jest.mock("../../api/api", () => ({
API: { current: { globalBulletinPath: "/api/stub" } }
}));
import { fetchBulletinContent } from "../actions";
describe("fetchBulletinContent()", () => {
it("fetches data", async () => {
expect(await fetchBulletinContent("slug")).toEqual({ foo: "bar" });
});
});

View File

@ -9,28 +9,28 @@ const FIRMWARE_MISSING_ALERT: Alert = {
created_at: 123,
problem_tag: "farmbot_os.firmware.missing",
priority: 100,
uuid: "uuid",
slug: "slug",
};
const SEED_DATA_MISSING_ALERT: Alert = {
created_at: 123,
problem_tag: "api.seed_data.missing",
priority: 300,
uuid: "uuid",
slug: "slug",
};
const UNKNOWN_ALERT: Alert = {
created_at: 123,
problem_tag: "farmbot_os.firmware.alert",
priority: 200,
uuid: "uuid",
slug: "slug",
};
const UNKNOWN_ALERT_2: Alert = {
created_at: 456,
problem_tag: "farmbot_os.firmware.alert",
priority: 100,
uuid: "uuid",
slug: "slug",
};
describe("<Alerts />", () => {
@ -84,8 +84,8 @@ describe("<FirmwareAlerts />", () => {
it("renders alerts", () => {
const p = fakeProps();
p.bot.hardware.alerts = {
"uuid1": FIRMWARE_MISSING_ALERT,
"uuid2": UNKNOWN_ALERT
"slug1": FIRMWARE_MISSING_ALERT,
"slug2": UNKNOWN_ALERT
};
const wrapper = mount(<FirmwareAlerts {...p} />);
expect(wrapper.text()).toContain("1");

View File

@ -2,10 +2,22 @@ jest.mock("../../devices/actions", () => ({ updateConfig: jest.fn() }));
jest.mock("../../api/crud", () => ({ destroy: jest.fn() }));
const mockData: Bulletin = {
content: "Alert content.",
href: "https://farm.bot",
href_label: "See more",
type: "info",
slug: "slug",
title: "Announcement",
};
jest.mock("../actions", () => ({
fetchBulletinContent: jest.fn(() => Promise.resolve(mockData)),
}));
import * as React from "react";
import { mount } from "enzyme";
import { AlertCard, changeFirmwareHardware } from "../cards";
import { AlertCardProps } from "../interfaces";
import { AlertCardProps, Bulletin } from "../interfaces";
import { fakeTimeSettings } from "../../__test_support__/fake_time_settings";
import { FBSelect } from "../../ui";
import { destroy } from "../../api/crud";
@ -17,7 +29,7 @@ describe("<AlertCard />", () => {
created_at: 123,
problem_tag: "author.noun.verb",
priority: 100,
uuid: "uuid",
slug: "slug",
},
apiFirmwareValue: undefined,
timeSettings: fakeTimeSettings(),
@ -70,6 +82,35 @@ describe("<AlertCard />", () => {
const wrapper = mount(<AlertCard {...p} />);
expect(wrapper.text()).toContain("Learn");
});
it("renders loading bulletin card", () => {
const p = fakeProps();
p.alert.problem_tag = "api.bulletin.unread";
const wrapper = mount(<AlertCard {...p} />);
["Loading...", "Slug"].map(string =>
expect(wrapper.text()).toContain(string));
});
it("renders loaded bulletin card", async () => {
const p = fakeProps();
p.alert.problem_tag = "api.bulletin.unread";
mockData.href_label = "See more";
mockData.type = "info";
const wrapper = await mount(<AlertCard {...p} />);
["Loading...", "Slug"].map(string =>
expect(wrapper.text()).not.toContain(string));
["Announcement", "Alert content.", "See more"].map(string =>
expect(wrapper.text()).toContain(string));
});
it("renders loaded bulletin card with missing fields", async () => {
const p = fakeProps();
p.alert.problem_tag = "api.bulletin.unread";
mockData.href_label = undefined;
mockData.type = "unknown";
const wrapper = await mount(<AlertCard {...p} />);
expect(wrapper.text()).toContain("Find out more");
});
});
describe("changeFirmwareHardware()", () => {

View File

@ -27,7 +27,7 @@ describe("<Messages />", () => {
created_at: 123,
problem_tag: "author.noun.verb",
priority: 100,
uuid: "uuid",
slug: "slug",
}];
const wrapper = mount(<Messages {...p} />);
expect(wrapper.text()).toContain("Message Center");

View File

@ -0,0 +1,11 @@
import axios from "axios";
import { API } from "../api";
import { Bulletin } from "./interfaces";
const url = (slug: string) => `${API.current.globalBulletinPath}/${slug}`;
export const fetchBulletinContent = (slug: string): Promise<Bulletin> => {
return axios
.get<Bulletin>(url(slug))
.then(response => Promise.resolve(response.data));
};

View File

@ -4,7 +4,9 @@ import {
AlertCardProps, AlertCardTemplateProps, FirmwareMissingProps,
SeedDataMissingProps, SeedDataMissingState, TourNotTakenProps,
CommonAlertCardProps,
DismissAlertProps
DismissAlertProps,
Bulletin,
BulletinAlertState
} from "./interfaces";
import { formatLogTime } from "../logs";
import {
@ -19,6 +21,8 @@ import {
isFwHardwareValue
} from "../devices/components/fbos_settings/board_type";
import { updateConfig } from "../devices/actions";
import { fetchBulletinContent } from "./actions";
import { startCase } from "lodash";
export const AlertCard = (props: AlertCardProps) => {
const { alert, timeSettings, findApiAlertById, dispatch } = props;
@ -37,6 +41,8 @@ export const AlertCard = (props: AlertCardProps) => {
return <UserNotWelcomed {...commonProps} />;
case "api.documentation.unread":
return <DocumentationUnread {...commonProps} />;
case "api.bulletin.unread":
return <BulletinAlert {...commonProps} />;
default:
return <UnknownAlert {...commonProps} />;
}
@ -62,6 +68,52 @@ const AlertCardTemplate = (props: AlertCardTemplateProps) => {
</div>;
};
const ICON_LOOKUP: { [x: string]: string } = {
"info": "info-circle",
"success": "check-square",
"warn": "exclamation-triangle",
};
class BulletinAlert
extends React.Component<CommonAlertCardProps, BulletinAlertState> {
state: BulletinAlertState = { bulletin: undefined };
componentDidMount() {
fetchBulletinContent(this.props.alert.slug)
.then(bulletin => this.setState({ bulletin }));
}
get bulletinData(): Bulletin {
return this.state.bulletin || {
content: t("Loading..."),
href: undefined,
href_label: undefined,
type: "info",
slug: this.props.alert.slug,
title: undefined,
};
}
render() {
const { content, href, href_label, type, title } = this.bulletinData;
return <AlertCardTemplate
alert={this.props.alert}
className={"bulletin-alert"}
title={title || startCase(this.props.alert.slug)}
iconName={ICON_LOOKUP[type] || "info"}
message={t(content)}
timeSettings={this.props.timeSettings}
dispatch={this.props.dispatch}
findApiAlertById={this.props.findApiAlertById}>
{href && <a className="link-button fb-button green"
href={href} target="_blank"
title={t("Open link in a new tab")}>
{href_label || t("Find out more")}
</a>}
</AlertCardTemplate>;
}
}
const UnknownAlert = (props: CommonAlertCardProps) => {
const { problem_tag, created_at, priority } = props.alert;
const { author, noun, verb } = splitProblemTag(problem_tag);

View File

@ -82,3 +82,16 @@ export interface SeedDataMissingState {
export interface TourNotTakenProps extends CommonAlertCardProps {
dispatch: Function;
}
export interface Bulletin {
content: string;
href: string | undefined;
href_label: string | undefined;
type: string;
slug: string;
title: string | undefined;
}
export interface BulletinAlertState {
bulletin: Bulletin | undefined;
}

View File

@ -43,7 +43,7 @@
"coveralls": "3.0.3",
"enzyme": "3.9.0",
"enzyme-adapter-react-16": "1.12.1",
"farmbot": "7.0.4",
"farmbot": "7.0.6",
"farmbot-toastr": "1.0.3",
"i18next": "15.0.9",
"jest": "24.7.1",