diff --git a/ui/src/AppBoot.js b/ui/src/AppBoot.js
new file mode 100644
index 000000000..1a2551afa
--- /dev/null
+++ b/ui/src/AppBoot.js
@@ -0,0 +1,58 @@
+// helpers used to bootstrap App instance and environment for it
+
+import Raven from "raven-js";
+
+const SettingsElement = () => document.getElementById("settings");
+
+const SetupRaven = settingsElement => {
+ if (
+ settingsElement !== null &&
+ settingsElement.dataset.ravenDsn &&
+ settingsElement.dataset.ravenDsn !== "{{ .SentryDSN }}"
+ ) {
+ let version = "unknown";
+ if (
+ settingsElement.dataset.version &&
+ settingsElement.dataset.version !== "{{ .Version }}"
+ ) {
+ version = settingsElement.dataset.version;
+ }
+
+ const ravenClient = new Raven.Client();
+ try {
+ ravenClient
+ .config(settingsElement.dataset.ravenDsn, {
+ release: version
+ })
+ .install();
+ } catch (err) {
+ console.error("Raven config failed: " + err);
+ }
+ return ravenClient;
+ }
+ return null;
+};
+
+const ParseDefaultFilters = settingsElement => {
+ let defaultFilters = [];
+ if (
+ settingsElement !== null &&
+ settingsElement.dataset.defaultFiltersBase64 &&
+ settingsElement.dataset.defaultFiltersBase64 !== "{{ .DefaultFilter }}"
+ ) {
+ // decode from base64 to a string
+ const decoded = Buffer.from(
+ settingsElement.dataset.defaultFiltersBase64,
+ "base64"
+ ).toString("ascii");
+ // parse decoded string as JSON
+ const json = JSON.parse(decoded);
+ // if we got an array then use it as default filters
+ if (Array.isArray(json)) {
+ defaultFilters = json;
+ }
+ }
+ return defaultFilters;
+};
+
+export { SettingsElement, SetupRaven, ParseDefaultFilters };
diff --git a/ui/src/AppBoot.test.js b/ui/src/AppBoot.test.js
new file mode 100644
index 000000000..705301c97
--- /dev/null
+++ b/ui/src/AppBoot.test.js
@@ -0,0 +1,90 @@
+import { SettingsElement, SetupRaven, ParseDefaultFilters } from "./AppBoot";
+
+const MockSettings = (version, ravenDsn, defaultFilters) => {
+ return jest.spyOn(document, "getElementById").mockImplementation(() => {
+ const filtersBase64 = btoa(JSON.stringify(defaultFilters));
+ const settings = document.createElement("span");
+ settings.id = "settings";
+ settings.dataset = {
+ version: version,
+ ravenDsn: ravenDsn,
+ defaultFiltersBase64: filtersBase64
+ };
+ return settings;
+ });
+};
+
+const RavenClient = (ravenDsn, version) => {
+ const settings = document.createElement("span");
+ settings.dataset = { ravenDsn: ravenDsn, version: version };
+ return SetupRaven(settings);
+};
+
+const FiltersSetting = filters => {
+ const settings = document.createElement("span");
+ settings.dataset = { defaultFiltersBase64: btoa(JSON.stringify(filters)) };
+ return ParseDefaultFilters(settings);
+};
+
+describe("SettingsElement()", () => {
+ it("returns null when '#settings' is missing", () => {
+ const settings = SettingsElement();
+ expect(settings).toBeNull();
+ });
+
+ it("returns correct span when '#settings' is present", () => {
+ const spy = MockSettings("ver1", "fakeDSN", []);
+ const settings = SettingsElement();
+ expect(spy).toHaveBeenCalledTimes(1);
+ expect(settings.id).toBe("settings");
+ expect(settings.dataset.version).toBe("ver1");
+ expect(settings.dataset.ravenDsn).toBe("fakeDSN");
+ });
+});
+
+describe("SetupRaven()", () => {
+ it("does nothing when raven DSN is missing", () => {
+ const client = RavenClient("");
+ expect(client).toBeNull();
+ });
+
+ it("configures raven when DSN is present", () => {
+ const client = RavenClient("https://key@example.com/mock");
+ expect(client.isSetup()).toBeTruthy();
+ expect(client._dsn).toBe("https://key@example.com/mock");
+ });
+
+ it("passes release option when version attr is present", () => {
+ const client = RavenClient("https://key@example.com/mock", "ver1");
+ expect(client.isSetup()).toBeTruthy();
+ expect(client._globalOptions.release).toBe("ver1");
+ });
+
+ it("logs an error when invalid DSN is passed to raven", () => {
+ const consoleSpy = jest.spyOn(console, "error");
+ const client = RavenClient("invalidDSN");
+ expect(client.isSetup()).toBeFalsy();
+ expect(consoleSpy).toHaveBeenCalledTimes(1);
+ });
+});
+
+describe("ParseDefaultFilters()", () => {
+ it("returns [] on missing filters attr", () => {
+ const settings = document.createElement("span");
+ settings.dataset = {};
+ expect(ParseDefaultFilters(settings)).toHaveLength(0);
+ });
+
+ it("returns [] on empty filters attr", () => {
+ const settings = document.createElement("span");
+ settings.dataset = { defaultFiltersBase64: "" };
+ expect(ParseDefaultFilters(settings)).toHaveLength(0);
+ });
+
+ it("returns decoded filters", () => {
+ const filters = FiltersSetting(["foo=bar", "bar=~baz"]);
+ expect(filters).toHaveLength(2);
+ expect(filters).toContain("foo=bar");
+ expect(filters).toContain("bar=~baz");
+ });
+});
diff --git a/ui/src/index.js b/ui/src/index.js
index 7643947bc..6786560c4 100644
--- a/ui/src/index.js
+++ b/ui/src/index.js
@@ -1,61 +1,21 @@
import React from "react";
import ReactDOM from "react-dom";
-import Raven from "raven-js";
-
import Moment from "react-moment";
+import { SettingsElement, SetupRaven, ParseDefaultFilters } from "./AppBoot";
import { App } from "./App";
-let defaultFilters = [];
+const settingsElement = SettingsElement();
-// check if we have early settings
-const settings = document.getElementById("settings");
-if (settings !== null) {
- // sentry setup if sentry dsn is set
- if (
- settings.dataset.ravenDsn &&
- settings.dataset.ravenDsn !== "{{ .SentryDSN }}"
- ) {
- let version = "unknown";
- if (
- settings.dataset.version &&
- settings.dataset.version !== "{{ .Version }}"
- ) {
- version = settings.dataset.version;
- }
-
- try {
- Raven.config(settings.dataset.ravenDsn, { release: version }).install();
- } catch (err) {
- console.error("Raven config failed: " + err);
- }
- }
-
- // default filters, JSON blob encoded with base64
- if (
- settings.dataset.defaultFiltersBase64 &&
- settings.dataset.defaultFiltersBase64 !== "{{ .DefaultFilter }}"
- ) {
- // decode from base64 to a string
- const decoded = Buffer.from(
- settings.dataset.defaultFiltersBase64,
- "base64"
- ).toString("ascii");
- // parse decoded string as JSON
- const json = JSON.parse(decoded);
- // if we got an array then use it as default filters
- if (Array.isArray(json)) {
- defaultFilters = json;
- }
- }
-}
+SetupRaven(settingsElement);
// global timer for updating timestamps to human readable offsets
// this needs to be run before any instance
// https://www.npmjs.com/package/react-moment#pooled-timer
Moment.startPooledTimer();
+const defaultFilters = ParseDefaultFilters(settingsElement);
ReactDOM.render(
,
document.getElementById("root")