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")