feat(ui): theme switching

This add "proper" dark mode using darkly bootstrap theme
This commit is contained in:
Łukasz Mierzwa
2019-11-23 17:23:43 +00:00
parent 45bff5a6ea
commit de43f1ac4a
135 changed files with 1295 additions and 905 deletions

View File

@@ -1,4 +1,10 @@
import { configure, getStorybook, setAddon } from "@storybook/react";
import React from "react";
import {
configure,
getStorybook,
setAddon,
addDecorator
} from "@storybook/react";
import createPercyAddon from "@percy-io/percy-storybook";
@@ -10,6 +16,12 @@ setAddon(percyAddon);
// mock date so the silence form always shows same preview
advanceTo(new Date(Date.UTC(2018, 7, 14, 17, 36, 40)));
addDecorator(story => {
document.body.classList.add("theme-light");
document.body.style = "";
return story();
});
const req = require.context("../src/Components", true, /\.stories\.(js|tsx)$/);
function loadStories() {

View File

@@ -1,32 +0,0 @@
// bundled font assets, so we don't need to talk to Google Fonts API
@import "src/Fonts.scss";
@import "Theme.scss";
@import "~bootstrap/scss/bootstrap";
@import "~bootswatch/dist/flatly/bootswatch";
// negative margin used for silence expiry badges with progress
.nmb-05 {
margin-bottom: -($spacer * 0.125) !important;
}
// this is used for navbar, to make it transparent
.bg-primary-transparent {
background-color: rgba($primary, 0.95);
}
// version for modals
.bg-primary-transparent-80 {
background-color: rgba($dark, 0.8);
}
.cursor-pointer {
cursor: pointer;
}
.cursor-text {
cursor: text;
}
.mw-1p {
min-width: 1%;
}

View File

@@ -1,6 +1,6 @@
import React from "react";
import { shallow } from "enzyme";
import { shallow, mount } from "enzyme";
import { NewUnappliedFilter } from "Stores/AlertStore";
import { App } from "./App";
@@ -18,12 +18,15 @@ beforeEach(() => {
// createing App instance will push current filters into window.location
// ensure it's wiped after each test
window.history.pushState({}, "App", "/");
document.body.className = "";
});
afterEach(() => {
localStorage.setItem("savedFilters", "");
jest.restoreAllMocks();
window.history.pushState({}, "App", "/");
document.body.className = "";
});
describe("<App />", () => {
@@ -155,7 +158,19 @@ describe("<App />", () => {
window.onpopstate(event);
});
it("appends 'dark-theme' class to #root if dark mode is enabled", () => {
it("appends correct theme class to #root if dark mode is disabled", () => {
const tree = shallow(
<App
defaultFilters={["foo=bar"]}
uiDefaults={Object.assign({}, uiDefaults, { DarkMode: false })}
/>
);
tree.instance().componentWillUnmount();
expect(document.body.className.split(" ")).toContain("theme-light");
});
it("appends 'theme-dark' class to #root if dark mode is enabled", () => {
const tree = shallow(
<App
defaultFilters={["foo=bar"]}
@@ -164,6 +179,22 @@ describe("<App />", () => {
);
tree.instance().componentWillUnmount();
expect(document.body.className.split(" ")).toContain("dark-theme");
expect(document.body.className.split(" ")).toContain("theme-dark");
});
it("toggling settingsStore.themeConfig.config.darkTheme modifies the theme", () => {
const tree = mount(
<App
defaultFilters={["foo=bar"]}
uiDefaults={Object.assign({}, uiDefaults, { DarkMode: false })}
/>
);
tree.update();
expect(document.body.className.split(" ")).toContain("theme-light");
tree.instance().settingsStore.themeConfig.config.darkTheme = true;
tree.update();
expect(document.body.className.split(" ")).toContain("theme-dark");
tree.instance().componentWillUnmount();
});
});

View File

@@ -1,5 +1,7 @@
import React, { Component } from "react";
import { observer } from "mobx-react";
import { AlertStore, DecodeLocationSearch } from "Stores/AlertStore";
import { Settings } from "Stores/Settings";
import { SilenceFormStore } from "Stores/SilenceFormStore";
@@ -7,10 +9,12 @@ import { NavBar } from "Components/NavBar";
import { Grid } from "Components/Grid";
import { Fetcher } from "Components/Fetcher";
import { FaviconBadge } from "Components/FaviconBadge";
import { ReactSelectColors, ReactSelectStyles } from "Components/MultiSelect";
import { Theme, ThemeContext } from "Components/Theme";
import { ErrorBoundary } from "./ErrorBoundary";
import "./App.scss";
import "./DarkTheme.scss";
import "Styles/ResetCSS.scss";
import "Styles/App.scss";
interface UIDefaults {
Refresh: number;
@@ -27,81 +31,98 @@ interface AppProps {
uiDefaults: UIDefaults;
}
class App extends Component<AppProps, {}> {
alertStore: AlertStore;
silenceFormStore: SilenceFormStore;
settingsStore: Settings;
filters: Array<string> = [];
const App = observer(
class App extends Component<AppProps, {}> {
alertStore: AlertStore;
silenceFormStore: SilenceFormStore;
settingsStore: Settings;
filters: Array<string> = [];
constructor(props: AppProps) {
super(props);
constructor(props: AppProps) {
super(props);
const { defaultFilters, uiDefaults } = this.props;
const { defaultFilters, uiDefaults } = this.props;
this.silenceFormStore = new SilenceFormStore();
this.settingsStore = new Settings(uiDefaults);
this.silenceFormStore = new SilenceFormStore();
this.settingsStore = new Settings(uiDefaults);
let filters;
this.state = { darkTheme: false };
// parse and decode request query args
const p = DecodeLocationSearch(window.location.search);
let filters;
// p.defaultsUsed means that karma URI didn't have ?q=foo query args
if (p.defaultsUsed) {
// no ?q=foo set, use defaults saved by the user or from backend config
if (this.settingsStore.savedFilters.config.present) {
filters = this.settingsStore.savedFilters.config.filters;
// parse and decode request query args
const p = DecodeLocationSearch(window.location.search);
// p.defaultsUsed means that karma URI didn't have ?q=foo query args
if (p.defaultsUsed) {
// no ?q=foo set, use defaults saved by the user or from backend config
if (this.settingsStore.savedFilters.config.present) {
filters = this.settingsStore.savedFilters.config.filters;
} else {
filters = defaultFilters;
}
} else {
filters = defaultFilters;
// user passed ?q=foo, use it as initial filters
filters = p.params.q;
}
} else {
// user passed ?q=foo, use it as initial filters
filters = p.params.q;
this.alertStore = new AlertStore(filters);
}
this.alertStore = new AlertStore(filters);
onPopState = (event: PopStateEvent) => {
event.preventDefault();
const p = DecodeLocationSearch(window.location.search);
this.alertStore.filters.setWithoutLocation(p.params.q);
};
componentDidMount() {
window.onpopstate = this.onPopState;
document.body.classList.toggle(
"theme-dark",
this.settingsStore.themeConfig.config.darkTheme
);
document.body.classList.toggle(
"theme-light",
!this.settingsStore.themeConfig.config.darkTheme
);
}
componentWillUnmount() {
window.onpopstate = () => {};
}
render() {
return (
<ErrorBoundary>
<Theme settingsStore={this.settingsStore} />
<ThemeContext.Provider
value={{
reactSelectStyles: this.settingsStore.themeConfig.config.darkTheme
? ReactSelectStyles(ReactSelectColors.Dark)
: ReactSelectStyles(ReactSelectColors.Light)
}}
>
<NavBar
alertStore={this.alertStore}
settingsStore={this.settingsStore}
silenceFormStore={this.silenceFormStore}
/>
<Grid
alertStore={this.alertStore}
settingsStore={this.settingsStore}
silenceFormStore={this.silenceFormStore}
/>
</ThemeContext.Provider>
<FaviconBadge alertStore={this.alertStore} />
<Fetcher
alertStore={this.alertStore}
settingsStore={this.settingsStore}
/>
</ErrorBoundary>
);
}
}
onPopState = (event: PopStateEvent) => {
event.preventDefault();
const p = DecodeLocationSearch(window.location.search);
this.alertStore.filters.setWithoutLocation(p.params.q);
};
componentDidMount() {
window.onpopstate = this.onPopState;
document.body.classList.toggle(
"dark-theme",
this.settingsStore.themeConfig.config.darkTheme
);
}
componentWillUnmount() {
window.onpopstate = () => {};
}
render() {
return (
<ErrorBoundary>
<FaviconBadge alertStore={this.alertStore} />
<NavBar
alertStore={this.alertStore}
settingsStore={this.settingsStore}
silenceFormStore={this.silenceFormStore}
/>
<Grid
alertStore={this.alertStore}
settingsStore={this.settingsStore}
silenceFormStore={this.silenceFormStore}
/>
<Fetcher
alertStore={this.alertStore}
settingsStore={this.settingsStore}
/>
</ErrorBoundary>
);
}
}
);
export { App };

View File

@@ -6,8 +6,6 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faChevronUp } from "@fortawesome/free-solid-svg-icons/faChevronUp";
import { faChevronDown } from "@fortawesome/free-solid-svg-icons/faChevronDown";
import "./index.scss";
const Trigger = ({ text, isOpen }) => (
<div className="d-flex flex-row justify-content-between">
<div>{text}</div>
@@ -28,7 +26,7 @@ const Accordion = ({ text, content, extraProps }) => (
className="card"
openedClassName="card"
triggerClassName="card-header cursor-pointer border-bottom-0"
triggerOpenedClassName="card-header cursor-pointer bg-light"
triggerOpenedClassName="card-header cursor-pointer"
contentOuterClassName="collapse show"
contentInnerClassName="card-body my-2"
{...extraProps}

View File

@@ -1,3 +0,0 @@
.accordion > .Collapsible.card {
overflow: unset;
}

View File

@@ -2,6 +2,8 @@ import React from "react";
import { mount } from "enzyme";
import toDiffableHtml from "diffable-html";
import { advanceTo, clear } from "jest-date-mock";
import { MockAlertGroup, MockAlert } from "__mocks__/Alerts.js";
@@ -85,7 +87,7 @@ describe("<AlertAck />", () => {
it("uses faCheck icon when idle", () => {
const tree = MountedAlertAck();
expect(tree.html()).toMatch(/fa-check/);
expect(toDiffableHtml(tree.html())).toMatch(/fa-check/);
});
it("uses faExclamationCircle after failed fetch", async () => {
@@ -96,7 +98,7 @@ describe("<AlertAck />", () => {
await expect(
tree.instance().submitState.silencesByCluster["default"].fetch
).resolves.toBeUndefined();
expect(tree.html()).toMatch(/fa-exclamation-circle/);
expect(toDiffableHtml(tree.html())).toMatch(/fa-exclamation-circle/);
});
it("[v1] uses faCheckCircle after successful fetch", async () => {
@@ -109,7 +111,7 @@ describe("<AlertAck />", () => {
await expect(
tree.instance().submitState.silencesByCluster["default"].fetch
).resolves.toBeUndefined();
expect(tree.html()).toMatch(/fa-check-circle/);
expect(toDiffableHtml(tree.html())).toMatch(/fa-check-circle/);
});
it("[v2] uses faCheckCircle after successful fetch", async () => {
@@ -121,7 +123,7 @@ describe("<AlertAck />", () => {
await expect(
tree.instance().submitState.silencesByCluster["default"].fetch
).resolves.toBeUndefined();
expect(tree.html()).toMatch(/fa-check-circle/);
expect(toDiffableHtml(tree.html())).toMatch(/fa-check-circle/);
});
it("sends a request on click", () => {

View File

@@ -3,8 +3,6 @@ import PropTypes from "prop-types";
import { CSSTransition } from "react-transition-group";
import "./index.css";
const DropdownSlide = ({ children, duration, ...props }) => (
<CSSTransition
classNames="components-animation-slide"

View File

@@ -3,8 +3,6 @@ import PropTypes from "prop-types";
import { CSSTransition } from "react-transition-group";
import "./index.css";
const MountFade = ({ children, duration, ...props }) => (
<CSSTransition
classNames="components-animation-fade"

View File

@@ -3,8 +3,6 @@ import PropTypes from "prop-types";
import { CSSTransition } from "react-transition-group";
import "./index.scss";
const MountModal = ({ children, duration, ...props }) => (
<CSSTransition
classNames="components-animation-modal"

View File

@@ -3,8 +3,6 @@ import PropTypes from "prop-types";
import { CSSTransition } from "react-transition-group";
import "./index.css";
const NavBarSlide = ({ children, duration, ...props }) => (
<CSSTransition
classNames="components-animation-navbar"

View File

@@ -2,7 +2,7 @@
exports[`<Alert /> matches snapshot when inhibited 1`] = `
"
<li class=\\"components-grid-alertgrid-alertgroup-alert list-group-item pl-1 pr-0 py-0 my-1 rounded-0 border-left-1 border-right-0 border-top-0 border-bottom-0 border-danger\\">
<li class=\\"components-grid-alertgrid-alertgroup-alert list-group-item bg-transparent pl-1 pr-0 py-0 my-1 rounded-0 border-left-1 border-right-0 border-top-0 border-bottom-0 border-danger\\">
<div>
<div class
style=\\"display: inline-block; max-width: 100%;\\"
@@ -162,7 +162,7 @@ exports[`<Alert /> matches snapshot when inhibited 1`] = `
exports[`<Alert /> matches snapshot with showAlertmanagers=false showReceiver=false 1`] = `
"
<li class=\\"components-grid-alertgrid-alertgroup-alert list-group-item pl-1 pr-0 py-0 my-1 rounded-0 border-left-1 border-right-0 border-top-0 border-bottom-0 border-danger\\">
<li class=\\"components-grid-alertgrid-alertgroup-alert list-group-item bg-transparent pl-1 pr-0 py-0 my-1 rounded-0 border-left-1 border-right-0 border-top-0 border-bottom-0 border-danger\\">
<div>
<div class
style=\\"display: inline-block; max-width: 100%;\\"

View File

@@ -17,8 +17,6 @@ import { RenderNonLinkAnnotation, RenderLinkAnnotation } from "../Annotation";
import { AlertMenu } from "./AlertMenu";
import { RenderSilence } from "../Silences";
import "./index.scss";
const Alert = observer(
class Alert extends Component {
static propTypes = {
@@ -46,7 +44,7 @@ const Alert = observer(
let classNames = [
"components-grid-alertgrid-alertgroup-alert",
"list-group-item",
"list-group-item bg-transparent",
"pl-1 pr-0 py-0",
"my-1",
"rounded-0",

View File

@@ -1,12 +0,0 @@
@import "src/Theme.scss";
.components-grid-alertgrid-alertgroup-alert {
&:hover {
background-color: $gray-100;
}
&.list-group-item {
border-width: 3px;
line-height: 1rem;
}
}

View File

@@ -16,8 +16,6 @@ import { faSearchMinus } from "@fortawesome/free-solid-svg-icons/faSearchMinus";
import { AlertStore } from "Stores/AlertStore";
import { TooltipWrapper } from "Components/TooltipWrapper";
import "./index.css";
const RenderNonLinkAnnotation = observer(
class RenderNonLinkAnnotation extends Component {
static propTypes = {

View File

@@ -79,7 +79,7 @@ describe("<RenderNonLinkAnnotation />", () => {
it("contains value when visible=true", () => {
const tree = ShallowNonLinkAnnotation(true);
expect(tree.html()).toMatch(/some long text/);
expect(toDiffableHtml(tree.html())).toMatch(/some long text/);
});
it("matches snapshot when visible=false", () => {
@@ -89,7 +89,7 @@ describe("<RenderNonLinkAnnotation />", () => {
it("doesn't contain value when visible=false", () => {
const tree = ShallowNonLinkAnnotation(false);
expect(tree.html()).not.toMatch(/some long text/);
expect(toDiffableHtml(tree.html())).not.toMatch(/some long text/);
});
it("links inside annotation are rendered as a.href", () => {
@@ -100,19 +100,19 @@ describe("<RenderNonLinkAnnotation />", () => {
it("clicking on - icon hides the value", () => {
const tree = MountedNonLinkAnnotation(true);
expect(tree.html()).toMatch(/fa-search-minus/);
expect(tree.html()).toMatch(/some long text/);
expect(toDiffableHtml(tree.html())).toMatch(/fa-search-minus/);
expect(toDiffableHtml(tree.html())).toMatch(/some long text/);
tree.find(".fa-search-minus").simulate("click");
expect(tree.html()).toMatch(/fa-search-plus/);
expect(tree.html()).not.toMatch(/some long text/);
expect(toDiffableHtml(tree.html())).toMatch(/fa-search-plus/);
expect(toDiffableHtml(tree.html())).not.toMatch(/some long text/);
});
it("clicking on + icon shows the value", () => {
const tree = MountedNonLinkAnnotation(false);
expect(tree.html()).toMatch(/fa-search-plus/);
expect(tree.html()).not.toMatch(/some long text/);
expect(toDiffableHtml(tree.html())).toMatch(/fa-search-plus/);
expect(toDiffableHtml(tree.html())).not.toMatch(/some long text/);
tree.find(".components-grid-annotation").simulate("click");
expect(tree.html()).toMatch(/fa-search-minus/);
expect(tree.html()).toMatch(/some long text/);
expect(toDiffableHtml(tree.html())).toMatch(/fa-search-minus/);
expect(toDiffableHtml(tree.html())).toMatch(/some long text/);
});
});

View File

@@ -2,7 +2,7 @@
exports[`<GroupFooter /> matches snapshot 1`] = `
"
<div class=\\"card-footer bg-card-footer-default px-2 py-1\\">
<div class=\\"card-footer components-grid-alertgrid-alertgroup-footer px-2 py-1\\">
<div class=\\"mb-1\\">
<div class
style=\\"display: inline-block; max-width: 100%;\\"
@@ -147,7 +147,7 @@ exports[`<GroupFooter /> matches snapshot 1`] = `
exports[`<GroupFooter /> mathes snapshot when silence is rendered 1`] = `
"
<div class=\\"card-footer bg-card-footer-default px-2 py-1\\">
<div class=\\"card-footer components-grid-alertgrid-alertgroup-footer px-2 py-1\\">
<div class=\\"mb-1\\">
<div class
style=\\"display: inline-block; max-width: 100%;\\"

View File

@@ -1,13 +0,0 @@
.components-grid-alertgrid-alertgroup-shared-silence {
border-width: 3px;
border-style: solid;
}
.components-grid-alertgrid-alertgroup-shared-silence > .card {
background-color: inherit;
}
/* this is default card background color, can't access it as scss variable */
.card-footer.bg-card-footer-default {
background-color: rgb(247, 247, 247);
}

View File

@@ -11,8 +11,6 @@ import { FilteringLabel } from "Components/Labels/FilteringLabel";
import { RenderNonLinkAnnotation, RenderLinkAnnotation } from "../Annotation";
import { RenderSilence } from "../Silences";
import "./index.css";
const GroupFooter = observer(
class GroupFooter extends Component {
static propTypes = {
@@ -33,7 +31,7 @@ const GroupFooter = observer(
} = this.props;
return (
<div className="card-footer bg-card-footer-default px-2 py-1">
<div className="card-footer components-grid-alertgrid-alertgroup-footer px-2 py-1">
<div className="mb-1">
{group.shared.annotations
.filter(a => a.isLink === false)

View File

@@ -25,11 +25,7 @@ import { GroupFooter } from "./GroupFooter";
const LoadButton = ({ icon, action, tooltip }) => {
return (
<TooltipWrapper title={tooltip}>
<button
type="button"
className="btn btn-sm py-0 bg-white"
onClick={action}
>
<button type="button" className="btn btn-sm py-0" onClick={action}>
<FontAwesomeIcon className="text-muted" icon={icon} />
</button>
</TooltipWrapper>
@@ -224,7 +220,7 @@ const AlertGroup = observer(
setIsMenuOpen={this.renderConfig.setIsMenuOpen}
/>
{this.collapse.value ? null : (
<div className="card-body px-2 py-1 bg-white components-grid-alertgrid-card">
<div className="card-body px-2 py-1 components-grid-alertgrid-card">
<ul className="list-group">
{group.alerts
.slice(0, this.renderConfig.alertsToRender)
@@ -244,7 +240,7 @@ const AlertGroup = observer(
/>
))}
{group.alerts.length > this.defaultRenderCount ? (
<li className="list-group-item border-0 p-0 text-center">
<li className="list-group-item border-0 p-0 text-center bg-transparent">
<LoadButton
icon={faMinus}
action={this.loadLess}

View File

@@ -21,8 +21,6 @@ import { SilenceFormStore } from "Stores/SilenceFormStore";
import { AlertGroup } from "./AlertGroup";
import { GridSizesConfig, GetGridElementWidth } from "./GridSize";
import "./index.css";
const AlertGrid = observer(
class AlertGrid extends Component {
static propTypes = {

View File

@@ -2,7 +2,7 @@
exports[`<EmptyGrid /> matches snapshot 1`] = `
"
<h1 class=\\"display-1 text-secondary screen-center\\">
<h1 class=\\"display-1 text-placeholder screen-center\\">
<svg aria-hidden=\\"true\\"
focusable=\\"false\\"
data-prefix=\\"fas\\"

View File

@@ -8,7 +8,7 @@ import { MountFade } from "Components/Animations/MountFade";
import "./index.scss";
const EmptyGrid = () => (
<h1 className="display-1 text-secondary screen-center">
<h1 className="display-1 text-placeholder screen-center">
<MountFade in={true}>
<FontAwesomeIcon icon={faMugHot} style={{ fontSize: "14rem" }} />
</MountFade>

View File

@@ -10,7 +10,7 @@ import { FatalError } from "./FatalError";
import { UpgradeNeeded } from "./UpgradeNeeded";
import { Grid } from ".";
import "Percy.scss";
import "Styles/Percy.scss";
storiesOf("Grid", module)
.add("FatalError", () => {

View File

@@ -10,8 +10,6 @@ import {
} from "Common/Colors";
import { QueryOperators, FormatQuery, StaticLabels } from "Common/Query";
import "./index.scss";
const isBackgroundDark = brightness => brightness <= 125;
// base class for shared code, not used directly

View File

@@ -15,8 +15,6 @@ import { QueryOperators } from "Common/Query";
import { TooltipWrapper } from "Components/TooltipWrapper";
import { BaseLabel } from "Components/Labels/BaseLabel";
import "./index.scss";
const FilterInputLabel = observer(
class FilterInputLabel extends BaseLabel {
static propTypes = {
@@ -98,7 +96,7 @@ const FilterInputLabel = observer(
value={filter.raw}
propName="raw"
change={this.onChange}
classEditing="py-0 border-0 bg-light text-black rounded"
classEditing="py-0 border-0 editing rounded"
afterStart={alertStore.status.pause}
afterFinish={alertStore.status.resume}
/>

View File

@@ -7,8 +7,6 @@ import { QueryOperators } from "Common/Query";
import { AlertStore } from "Stores/AlertStore";
import { BaseLabel } from "Components/Labels/BaseLabel";
import "./index.css";
const HistoryLabel = observer(
class HistoryLabel extends BaseLabel {
static propTypes = {

View File

@@ -10,8 +10,6 @@ import { AlertStore } from "Stores/AlertStore";
import { QueryOperators, FormatQuery } from "Common/Query";
import { BaseLabel } from "Components/Labels/BaseLabel";
import "./index.scss";
const LabelWithPercent = observer(
class LabelWithPercent extends BaseLabel {
static propTypes = {

View File

@@ -90,16 +90,16 @@ describe("<LabelWithPercent />", () => {
it("uses bg-danger when percent is >66", () => {
const tree = MountedLabelWithPercent("foo", "bar", 25, 67, 0, false);
expect(tree.html()).toMatch(/progress-bar bg-danger/);
expect(toDiffableHtml(tree.html())).toMatch(/progress-bar bg-danger/);
});
it("uses bg-warning when percent is >33", () => {
const tree = MountedLabelWithPercent("foo", "bar", 25, 66, 0, false);
expect(tree.html()).toMatch(/progress-bar bg-warning/);
expect(toDiffableHtml(tree.html())).toMatch(/progress-bar bg-warning/);
});
it("uses bg-success when percent is <=33", () => {
const tree = MountedLabelWithPercent("foo", "bar", 25, 33, 0, false);
expect(tree.html()).toMatch(/progress-bar bg-success/);
expect(toDiffableHtml(tree.html())).toMatch(/progress-bar bg-success/);
});
});

View File

@@ -7,13 +7,14 @@ import { observer } from "mobx-react";
import Select from "react-select";
import { Settings } from "Stores/Settings";
import { ReactSelectStyles } from "Components/MultiSelect";
import { ThemeContext } from "Components/Theme";
const AlertGroupCollapseConfiguration = observer(
class AlertGroupCollapseConfiguration extends Component {
static propTypes = {
settingsStore: PropTypes.instanceOf(Settings).isRequired
};
static contextType = ThemeContext;
constructor(props) {
super(props);
@@ -56,7 +57,7 @@ const AlertGroupCollapseConfiguration = observer(
return (
<div className="form-group mb-0">
<Select
styles={ReactSelectStyles}
styles={this.context.reactSelectStyles}
classNamePrefix="react-select"
instanceId="configuration-collapse"
defaultValue={this.valueToOption(

View File

@@ -5,6 +5,8 @@ import { mount } from "enzyme";
import toDiffableHtml from "diffable-html";
import { Settings } from "Stores/Settings";
import { ThemeContext } from "Components/Theme";
import { ReactSelectColors, ReactSelectStyles } from "Components/MultiSelect";
import { AlertGroupCollapseConfiguration } from "./AlertGroupCollapseConfiguration";
let settingsStore;
@@ -14,7 +16,13 @@ beforeEach(() => {
const FakeConfiguration = () => {
return mount(
<AlertGroupCollapseConfiguration settingsStore={settingsStore} />
<ThemeContext.Provider
value={{
reactSelectStyles: ReactSelectStyles(ReactSelectColors.Light)
}}
>
<AlertGroupCollapseConfiguration settingsStore={settingsStore} />
</ThemeContext.Provider>
);
};

View File

@@ -8,8 +8,6 @@ import InputRange from "react-input-range";
import { Settings } from "Stores/Settings";
import "./InputRange.scss";
const AlertGroupConfiguration = observer(
class AlertGroupConfiguration extends Component {
static propTypes = {

View File

@@ -7,7 +7,7 @@ import { observer } from "mobx-react";
import Select from "react-select";
import { Settings } from "Stores/Settings";
import { ReactSelectStyles } from "Components/MultiSelect";
import { ThemeContext } from "Components/Theme";
import { SortLabelName } from "./SortLabelName";
const AlertGroupSortConfiguration = observer(
@@ -15,6 +15,7 @@ const AlertGroupSortConfiguration = observer(
static propTypes = {
settingsStore: PropTypes.instanceOf(Settings).isRequired
};
static contextType = ThemeContext;
constructor(props) {
super(props);
@@ -67,7 +68,7 @@ const AlertGroupSortConfiguration = observer(
<div className="d-flex flex-fill flex-lg-row flex-column justify-content-between">
<div className="flex-shrink-0 flex-grow-1 flex-basis-auto">
<Select
styles={ReactSelectStyles}
styles={this.context.reactSelectStyles}
classNamePrefix="react-select"
instanceId="configuration-sort-order"
defaultValue={this.valueToOption(

View File

@@ -5,6 +5,8 @@ import { mount } from "enzyme";
import toDiffableHtml from "diffable-html";
import { Settings } from "Stores/Settings";
import { ThemeContext } from "Components/Theme";
import { ReactSelectColors, ReactSelectStyles } from "Components/MultiSelect";
import { AlertGroupSortConfiguration } from "./AlertGroupSortConfiguration";
let settingsStore;
@@ -18,7 +20,15 @@ afterEach(() => {
});
const FakeConfiguration = () => {
return mount(<AlertGroupSortConfiguration settingsStore={settingsStore} />);
return mount(
<ThemeContext.Provider
value={{
reactSelectStyles: ReactSelectStyles(ReactSelectColors.Light)
}}
>
<AlertGroupSortConfiguration settingsStore={settingsStore} />
</ThemeContext.Provider>
);
};
const ExpandSortLabelSuggestions = async () => {

View File

@@ -10,8 +10,6 @@ import InputRange from "react-input-range";
import { Settings } from "Stores/Settings";
import "./InputRange.scss";
const AlertGroupWidthConfiguration = observer(
class AlertGroupWidthConfiguration extends Component {
static propTypes = {

View File

@@ -8,8 +8,6 @@ import InputRange from "react-input-range";
import { Settings } from "Stores/Settings";
import "./InputRange.scss";
const FetchConfiguration = observer(
class FetchConfiguration extends Component {
static propTypes = {

View File

@@ -10,7 +10,7 @@ import { StaticLabels } from "Common/Query";
import { FetchGet } from "Common/Fetch";
import { FormatBackendURI } from "Stores/AlertStore";
import { Settings } from "Stores/Settings";
import { ReactSelectStyles } from "Components/MultiSelect";
import { ThemeContext } from "Components/Theme";
const valueToOption = v => ({ label: v, value: v });
@@ -19,6 +19,7 @@ const SortLabelName = observer(
static propTypes = {
settingsStore: PropTypes.instanceOf(Settings).isRequired
};
static contextType = ThemeContext;
constructor(props) {
super(props);
@@ -71,7 +72,7 @@ const SortLabelName = observer(
return (
<Creatable
styles={ReactSelectStyles}
styles={this.context.reactSelectStyles}
classNamePrefix="react-select"
instanceId="configuration-sort-label"
defaultValue={valueToOption(

View File

@@ -17,9 +17,13 @@ const ThemeConfiguration = observer(
settingsStore.themeConfig.config.darkTheme = event.target.checked;
document.body.classList.toggle(
"dark-theme",
"theme-dark",
settingsStore.themeConfig.config.darkTheme
);
document.body.classList.toggle(
"theme-light",
!settingsStore.themeConfig.config.darkTheme
);
});
render() {

View File

@@ -4,9 +4,9 @@ exports[`<AlertGroupCollapseConfiguration /> matches snapshot with default value
"
<div class=\\"form-group mb-0\\">
<div class=\\" css-2b097c-container\\">
<div class=\\"react-select__control css-au8pbo-control\\">
<div class=\\"react-select__value-container react-select__value-container--has-value css-pb81dw\\">
<div class=\\"react-select__single-value css-1uccc91-singleValue\\">
<div class=\\"react-select__control css-r5n82u-control\\">
<div class=\\"react-select__value-container react-select__value-container--has-value css-97xgis\\">
<div class=\\"react-select__single-value css-1wh03ml-singleValue\\">
Collapse on mobile
</div>
<div class=\\"css-b8ldur-Input\\">

View File

@@ -6,9 +6,9 @@ exports[`<AlertGroupSortConfiguration /> matches snapshot with default values 1`
<div class=\\"d-flex flex-fill flex-lg-row flex-column justify-content-between\\">
<div class=\\"flex-shrink-0 flex-grow-1 flex-basis-auto\\">
<div class=\\" css-2b097c-container\\">
<div class=\\"react-select__control css-au8pbo-control\\">
<div class=\\"react-select__value-container react-select__value-container--has-value css-pb81dw\\">
<div class=\\"react-select__single-value css-1uccc91-singleValue\\">
<div class=\\"react-select__control css-r5n82u-control\\">
<div class=\\"react-select__value-container react-select__value-container--has-value css-97xgis\\">
<div class=\\"react-select__single-value css-1wh03ml-singleValue\\">
Use defaults from karma config file
</div>
<div class=\\"css-b8ldur-Input\\">

View File

@@ -4,7 +4,7 @@ exports[`<Configuration /> matches snapshot 1`] = `
"
<form class=\\"px-3 accordion\\">
<div class=\\"Collapsible card\\">
<div class=\\"Collapsible__trigger is-open card-header cursor-pointer bg-light\\">
<div class=\\"Collapsible__trigger is-open card-header cursor-pointer\\">
<div class=\\"d-flex flex-row justify-content-between\\">
<div>
Refresh interval
@@ -28,7 +28,7 @@ exports[`<Configuration /> matches snapshot 1`] = `
</div>
</div>
<div class=\\"Collapsible__contentOuter collapse show\\"
style=\\"height:auto;-webkit-transition:none;-ms-transition:none;transition:none;overflow:visible\\"
style=\\"height: auto; transition: none; overflow: visible;\\"
>
<div class=\\"Collapsible__contentInner card-body my-2\\">
<div class=\\"form-group mb-0 text-center\\">
@@ -41,12 +41,12 @@ exports[`<Configuration /> matches snapshot 1`] = `
</span>
</span>
<div class=\\"input-range__track input-range__track--background\\">
<div style=\\"left:0%;width:18.181818181818183%\\"
<div style=\\"left: 0%; width: 18.181818181818183%;\\"
class=\\"input-range__track input-range__track--active\\"
>
</div>
<span class=\\"input-range__slider-container\\"
style=\\"position:absolute;left:18.181818181818183%\\"
style=\\"position: absolute; left: 18.181818181818183%;\\"
>
<span class=\\"input-range__label input-range__label--value\\">
<span class=\\"input-range__label-container\\">
@@ -75,7 +75,7 @@ exports[`<Configuration /> matches snapshot 1`] = `
</div>
</div>
<div class=\\"Collapsible card\\">
<div class=\\"Collapsible__trigger is-open card-header cursor-pointer bg-light\\">
<div class=\\"Collapsible__trigger is-open card-header cursor-pointer\\">
<div class=\\"d-flex flex-row justify-content-between\\">
<div>
Filter bar configuration
@@ -99,15 +99,15 @@ exports[`<Configuration /> matches snapshot 1`] = `
</div>
</div>
<div class=\\"Collapsible__contentOuter collapse show\\"
style=\\"height:auto;-webkit-transition:none;-ms-transition:none;transition:none;overflow:visible\\"
style=\\"height: auto; transition: none; overflow: visible;\\"
>
<div class=\\"Collapsible__contentInner card-body my-2\\">
<div class=\\"form-group mb-0\\">
<div class=\\"form-check form-check-inline\\">
<span class=\\"custom-control custom-switch\\">
<input type=\\"checkbox\\"
id=\\"configuration-autohide\\"
<input id=\\"configuration-autohide\\"
class=\\"custom-control-input\\"
type=\\"checkbox\\"
value
checked
>
@@ -123,7 +123,7 @@ exports[`<Configuration /> matches snapshot 1`] = `
</div>
</div>
<div class=\\"Collapsible card\\">
<div class=\\"Collapsible__trigger is-open card-header cursor-pointer bg-light\\">
<div class=\\"Collapsible__trigger is-open card-header cursor-pointer\\">
<div class=\\"d-flex flex-row justify-content-between\\">
<div>
Theme
@@ -147,15 +147,15 @@ exports[`<Configuration /> matches snapshot 1`] = `
</div>
</div>
<div class=\\"Collapsible__contentOuter collapse show\\"
style=\\"height:auto;-webkit-transition:none;-ms-transition:none;transition:none;overflow:visible\\"
style=\\"height: auto; transition: none; overflow: visible;\\"
>
<div class=\\"Collapsible__contentInner card-body my-2\\">
<div class=\\"form-group mb-2\\">
<div class=\\"form-check form-check-inline\\">
<span class=\\"custom-control custom-switch\\">
<input type=\\"checkbox\\"
id=\\"configuration-colortitlebar\\"
<input id=\\"configuration-colortitlebar\\"
class=\\"custom-control-input\\"
type=\\"checkbox\\"
value
>
<label class=\\"custom-control-label cursor-pointer mr-3\\"
@@ -169,9 +169,9 @@ exports[`<Configuration /> matches snapshot 1`] = `
<div class=\\"form-group mb-0\\">
<div class=\\"form-check form-check-inline\\">
<span class=\\"custom-control custom-switch\\">
<input type=\\"checkbox\\"
id=\\"configuration-theme\\"
<input id=\\"configuration-theme\\"
class=\\"custom-control-input\\"
type=\\"checkbox\\"
value
>
<label class=\\"custom-control-label cursor-pointer mr-3\\"
@@ -189,7 +189,7 @@ exports[`<Configuration /> matches snapshot 1`] = `
</div>
</div>
<div class=\\"Collapsible card\\">
<div class=\\"Collapsible__trigger is-open card-header cursor-pointer bg-light\\">
<div class=\\"Collapsible__trigger is-open card-header cursor-pointer\\">
<div class=\\"d-flex flex-row justify-content-between\\">
<div>
Minimal alert group width
@@ -213,7 +213,7 @@ exports[`<Configuration /> matches snapshot 1`] = `
</div>
</div>
<div class=\\"Collapsible__contentOuter collapse show\\"
style=\\"height:auto;-webkit-transition:none;-ms-transition:none;transition:none;overflow:visible\\"
style=\\"height: auto; transition: none; overflow: visible;\\"
>
<div class=\\"Collapsible__contentInner card-body my-2\\">
<div class=\\"form-group mb-0 text-center\\">
@@ -226,12 +226,12 @@ exports[`<Configuration /> matches snapshot 1`] = `
</span>
</span>
<div class=\\"input-range__track input-range__track--background\\">
<div style=\\"left:0%;width:24%\\"
<div style=\\"left: 0%; width: 24%;\\"
class=\\"input-range__track input-range__track--active\\"
>
</div>
<span class=\\"input-range__slider-container\\"
style=\\"position:absolute;left:24%\\"
style=\\"position: absolute; left: 24%;\\"
>
<span class=\\"input-range__label input-range__label--value\\">
<span class=\\"input-range__label-container\\">
@@ -260,7 +260,7 @@ exports[`<Configuration /> matches snapshot 1`] = `
</div>
</div>
<div class=\\"Collapsible card\\">
<div class=\\"Collapsible__trigger is-open card-header cursor-pointer bg-light\\">
<div class=\\"Collapsible__trigger is-open card-header cursor-pointer\\">
<div class=\\"d-flex flex-row justify-content-between\\">
<div>
Default number of alerts to show per group
@@ -284,7 +284,7 @@ exports[`<Configuration /> matches snapshot 1`] = `
</div>
</div>
<div class=\\"Collapsible__contentOuter collapse show\\"
style=\\"height:auto;-webkit-transition:none;-ms-transition:none;transition:none;overflow:visible\\"
style=\\"height: auto; transition: none; overflow: visible;\\"
>
<div class=\\"Collapsible__contentInner card-body my-2\\">
<div class=\\"form-group mb-0 text-center\\">
@@ -297,12 +297,12 @@ exports[`<Configuration /> matches snapshot 1`] = `
</span>
</span>
<div class=\\"input-range__track input-range__track--background\\">
<div style=\\"left:0%;width:44.44444444444444%\\"
<div style=\\"left: 0%; width: 44.44444444444444%;\\"
class=\\"input-range__track input-range__track--active\\"
>
</div>
<span class=\\"input-range__slider-container\\"
style=\\"position:absolute;left:44.44444444444444%\\"
style=\\"position: absolute; left: 44.44444444444444%;\\"
>
<span class=\\"input-range__label input-range__label--value\\">
<span class=\\"input-range__label-container\\">
@@ -331,7 +331,7 @@ exports[`<Configuration /> matches snapshot 1`] = `
</div>
</div>
<div class=\\"Collapsible card\\">
<div class=\\"Collapsible__trigger is-open card-header cursor-pointer bg-light\\">
<div class=\\"Collapsible__trigger is-open card-header cursor-pointer\\">
<div class=\\"d-flex flex-row justify-content-between\\">
<div>
Default alert group display
@@ -355,32 +355,32 @@ exports[`<Configuration /> matches snapshot 1`] = `
</div>
</div>
<div class=\\"Collapsible__contentOuter collapse show\\"
style=\\"height:auto;-webkit-transition:none;-ms-transition:none;transition:none;overflow:visible\\"
style=\\"height: auto; transition: none; overflow: visible;\\"
>
<div class=\\"Collapsible__contentInner card-body my-2\\">
<div class=\\"form-group mb-0\\">
<div class=\\" css-2b097c-container\\">
<div class=\\"react-select__control css-au8pbo-control\\">
<div class=\\"react-select__value-container react-select__value-container--has-value css-pb81dw\\">
<div class=\\"react-select__single-value css-1uccc91-singleValue\\">
<div class=\\"react-select__control css-r5n82u-control\\">
<div class=\\"react-select__value-container react-select__value-container--has-value css-97xgis\\">
<div class=\\"react-select__single-value css-1wh03ml-singleValue\\">
Collapse on mobile
</div>
<div class=\\"css-b8ldur-Input\\">
<div class=\\"react-select__input\\"
style=\\"display:inline-block\\"
style=\\"display: inline-block;\\"
>
<input type=\\"text\\"
autocapitalize=\\"none\\"
<input autocapitalize=\\"none\\"
autocomplete=\\"off\\"
autocorrect=\\"off\\"
id=\\"react-select-configuration-collapse-input\\"
spellcheck=\\"false\\"
tabindex=\\"0\\"
value
type=\\"text\\"
aria-autocomplete=\\"list\\"
style=\\"box-sizing:content-box;width:1px;label:input;background:0;border:0;font-size:inherit;opacity:1;outline:0;padding:0;color:inherit\\"
style=\\"box-sizing: content-box; width: 2px; border: 0px; font-size: inherit; opacity: 1; outline: 0; padding: 0px;\\"
value
>
<div style=\\"position:absolute;top:0;left:0;visibility:hidden;height:0;overflow:scroll;white-space:pre\\">
<div style=\\"position: absolute; top: 0px; left: 0px; visibility: hidden; height: 0px; overflow: scroll; white-space: pre; font-size: inherit; font-family: -webkit-small-control; letter-spacing: normal; text-transform: none;\\">
</div>
</div>
</div>
@@ -410,7 +410,7 @@ exports[`<Configuration /> matches snapshot 1`] = `
</div>
</div>
<div class=\\"Collapsible card\\">
<div class=\\"Collapsible__trigger is-open card-header cursor-pointer bg-light\\">
<div class=\\"Collapsible__trigger is-open card-header cursor-pointer\\">
<div class=\\"d-flex flex-row justify-content-between\\">
<div>
Grid sort order
@@ -434,34 +434,34 @@ exports[`<Configuration /> matches snapshot 1`] = `
</div>
</div>
<div class=\\"Collapsible__contentOuter collapse show\\"
style=\\"height:auto;-webkit-transition:none;-ms-transition:none;transition:none;overflow:visible\\"
style=\\"height: auto; transition: none; overflow: visible;\\"
>
<div class=\\"Collapsible__contentInner card-body my-2\\">
<div class=\\"form-group mb-0\\">
<div class=\\"d-flex flex-fill flex-lg-row flex-column justify-content-between\\">
<div class=\\"flex-shrink-0 flex-grow-1 flex-basis-auto\\">
<div class=\\" css-2b097c-container\\">
<div class=\\"react-select__control css-au8pbo-control\\">
<div class=\\"react-select__value-container react-select__value-container--has-value css-pb81dw\\">
<div class=\\"react-select__single-value css-1uccc91-singleValue\\">
<div class=\\"react-select__control css-r5n82u-control\\">
<div class=\\"react-select__value-container react-select__value-container--has-value css-97xgis\\">
<div class=\\"react-select__single-value css-1wh03ml-singleValue\\">
Use defaults from karma config file
</div>
<div class=\\"css-b8ldur-Input\\">
<div class=\\"react-select__input\\"
style=\\"display:inline-block\\"
style=\\"display: inline-block;\\"
>
<input type=\\"text\\"
autocapitalize=\\"none\\"
<input autocapitalize=\\"none\\"
autocomplete=\\"off\\"
autocorrect=\\"off\\"
id=\\"react-select-configuration-sort-order-input\\"
spellcheck=\\"false\\"
tabindex=\\"0\\"
value
type=\\"text\\"
aria-autocomplete=\\"list\\"
style=\\"box-sizing:content-box;width:1px;label:input;background:0;border:0;font-size:inherit;opacity:1;outline:0;padding:0;color:inherit\\"
style=\\"box-sizing: content-box; width: 2px; border: 0px; font-size: inherit; opacity: 1; outline: 0; padding: 0px;\\"
value
>
<div style=\\"position:absolute;top:0;left:0;visibility:hidden;height:0;overflow:scroll;white-space:pre\\">
<div style=\\"position: absolute; top: 0px; left: 0px; visibility: hidden; height: 0px; overflow: scroll; white-space: pre; font-size: inherit; font-family: -webkit-small-control; letter-spacing: normal; text-transform: none;\\">
</div>
</div>
</div>

View File

@@ -1,17 +1,25 @@
import React from "react";
import { shallow } from "enzyme";
import { mount } from "enzyme";
import toDiffableHtml from "diffable-html";
import { Settings } from "Stores/Settings";
import { ThemeContext } from "Components/Theme";
import { ReactSelectColors, ReactSelectStyles } from "Components/MultiSelect";
import { Configuration } from ".";
describe("<Configuration />", () => {
it("matches snapshot", () => {
const settingsStore = new Settings();
const tree = shallow(
<Configuration settingsStore={settingsStore} defaultIsOpen={true} />
const tree = mount(
<ThemeContext.Provider
value={{
reactSelectStyles: ReactSelectStyles(ReactSelectColors.Light)
}}
>
<Configuration settingsStore={settingsStore} defaultIsOpen={true} />
</ThemeContext.Provider>
);
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
});

View File

@@ -6,6 +6,8 @@ import toDiffableHtml from "diffable-html";
import { AlertStore } from "Stores/AlertStore";
import { Settings } from "Stores/Settings";
import { ThemeContext } from "Components/Theme";
import { ReactSelectColors, ReactSelectStyles } from "Components/MultiSelect";
import { MainModalContent } from "./MainModalContent";
let alertStore;
@@ -22,14 +24,26 @@ afterEach(() => {
jest.restoreAllMocks();
});
const Wrapped = component => (
<ThemeContext.Provider
value={{
reactSelectStyles: ReactSelectStyles(ReactSelectColors.Light)
}}
>
{component}
</ThemeContext.Provider>
);
const FakeModal = () => {
return mount(
<MainModalContent
alertStore={alertStore}
settingsStore={settingsStore}
onHide={onHide}
expandAllOptions={true}
/>
Wrapped(
<MainModalContent
alertStore={alertStore}
settingsStore={settingsStore}
onHide={onHide}
expandAllOptions={true}
/>
)
);
};
@@ -51,12 +65,14 @@ describe("<MainModalContent />", () => {
// https://github.com/airbnb/enzyme/issues/1213
const tree = mount(
<span>
<MainModalContent
alertStore={alertStore}
settingsStore={settingsStore}
onHide={onHide}
expandAllOptions={true}
/>
{Wrapped(
<MainModalContent
alertStore={alertStore}
settingsStore={settingsStore}
onHide={onHide}
expandAllOptions={true}
/>
)}
</span>
);
expect(toDiffableHtml(tree.html())).toMatchSnapshot();

View File

@@ -4,7 +4,7 @@ exports[`<Help /> matches snapshot 1`] = `
"
<div class=\\"accordion\\">
<div class=\\"Collapsible card\\">
<div class=\\"Collapsible__trigger is-open card-header cursor-pointer bg-light\\">
<div class=\\"Collapsible__trigger is-open card-header cursor-pointer\\">
<div class=\\"d-flex flex-row justify-content-between\\">
<div>
Fiter operators
@@ -163,7 +163,7 @@ exports[`<Help /> matches snapshot 1`] = `
</div>
</div>
<div class=\\"Collapsible card\\">
<div class=\\"Collapsible__trigger is-open card-header cursor-pointer bg-light\\">
<div class=\\"Collapsible__trigger is-open card-header cursor-pointer\\">
<div class=\\"d-flex flex-row justify-content-between\\">
<div>
Filtering using alert labels
@@ -339,7 +339,7 @@ exports[`<Help /> matches snapshot 1`] = `
</div>
</div>
<div class=\\"Collapsible card\\">
<div class=\\"Collapsible__trigger is-open card-header cursor-pointer bg-light\\">
<div class=\\"Collapsible__trigger is-open card-header cursor-pointer\\">
<div class=\\"d-flex flex-row justify-content-between\\">
<div>
Filtering alerts using special filters

View File

@@ -23,7 +23,7 @@ exports[`<MainModalContent /> matches snapshot 1`] = `
<div class=\\"modal-body\\">
<form class=\\"px-3 accordion\\">
<div class=\\"Collapsible card\\">
<div class=\\"Collapsible__trigger is-open card-header cursor-pointer bg-light\\">
<div class=\\"Collapsible__trigger is-open card-header cursor-pointer\\">
<div class=\\"d-flex flex-row justify-content-between\\">
<div>
Refresh interval
@@ -94,7 +94,7 @@ exports[`<MainModalContent /> matches snapshot 1`] = `
</div>
</div>
<div class=\\"Collapsible card\\">
<div class=\\"Collapsible__trigger is-open card-header cursor-pointer bg-light\\">
<div class=\\"Collapsible__trigger is-open card-header cursor-pointer\\">
<div class=\\"d-flex flex-row justify-content-between\\">
<div>
Filter bar configuration
@@ -142,7 +142,7 @@ exports[`<MainModalContent /> matches snapshot 1`] = `
</div>
</div>
<div class=\\"Collapsible card\\">
<div class=\\"Collapsible__trigger is-open card-header cursor-pointer bg-light\\">
<div class=\\"Collapsible__trigger is-open card-header cursor-pointer\\">
<div class=\\"d-flex flex-row justify-content-between\\">
<div>
Theme
@@ -208,7 +208,7 @@ exports[`<MainModalContent /> matches snapshot 1`] = `
</div>
</div>
<div class=\\"Collapsible card\\">
<div class=\\"Collapsible__trigger is-open card-header cursor-pointer bg-light\\">
<div class=\\"Collapsible__trigger is-open card-header cursor-pointer\\">
<div class=\\"d-flex flex-row justify-content-between\\">
<div>
Minimal alert group width
@@ -279,7 +279,7 @@ exports[`<MainModalContent /> matches snapshot 1`] = `
</div>
</div>
<div class=\\"Collapsible card\\">
<div class=\\"Collapsible__trigger is-open card-header cursor-pointer bg-light\\">
<div class=\\"Collapsible__trigger is-open card-header cursor-pointer\\">
<div class=\\"d-flex flex-row justify-content-between\\">
<div>
Default number of alerts to show per group
@@ -350,7 +350,7 @@ exports[`<MainModalContent /> matches snapshot 1`] = `
</div>
</div>
<div class=\\"Collapsible card\\">
<div class=\\"Collapsible__trigger is-open card-header cursor-pointer bg-light\\">
<div class=\\"Collapsible__trigger is-open card-header cursor-pointer\\">
<div class=\\"d-flex flex-row justify-content-between\\">
<div>
Default alert group display
@@ -379,9 +379,9 @@ exports[`<MainModalContent /> matches snapshot 1`] = `
<div class=\\"Collapsible__contentInner card-body my-2\\">
<div class=\\"form-group mb-0\\">
<div class=\\" css-2b097c-container\\">
<div class=\\"react-select__control css-au8pbo-control\\">
<div class=\\"react-select__value-container react-select__value-container--has-value css-pb81dw\\">
<div class=\\"react-select__single-value css-1uccc91-singleValue\\">
<div class=\\"react-select__control css-r5n82u-control\\">
<div class=\\"react-select__value-container react-select__value-container--has-value css-97xgis\\">
<div class=\\"react-select__single-value css-1wh03ml-singleValue\\">
Collapse on mobile
</div>
<div class=\\"css-b8ldur-Input\\">
@@ -429,7 +429,7 @@ exports[`<MainModalContent /> matches snapshot 1`] = `
</div>
</div>
<div class=\\"Collapsible card\\">
<div class=\\"Collapsible__trigger is-open card-header cursor-pointer bg-light\\">
<div class=\\"Collapsible__trigger is-open card-header cursor-pointer\\">
<div class=\\"d-flex flex-row justify-content-between\\">
<div>
Grid sort order
@@ -460,9 +460,9 @@ exports[`<MainModalContent /> matches snapshot 1`] = `
<div class=\\"d-flex flex-fill flex-lg-row flex-column justify-content-between\\">
<div class=\\"flex-shrink-0 flex-grow-1 flex-basis-auto\\">
<div class=\\" css-2b097c-container\\">
<div class=\\"react-select__control css-au8pbo-control\\">
<div class=\\"react-select__value-container react-select__value-container--has-value css-pb81dw\\">
<div class=\\"react-select__single-value css-1uccc91-singleValue\\">
<div class=\\"react-select__control css-r5n82u-control\\">
<div class=\\"react-select__value-container react-select__value-container--has-value css-97xgis\\">
<div class=\\"react-select__single-value css-1wh03ml-singleValue\\">
Use defaults from karma config file
</div>
<div class=\\"css-b8ldur-Input\\">

View File

@@ -62,7 +62,7 @@ const MainModal = observer(
<Modal isOpen={this.toggle.show} toggleOpen={this.toggle.toggle}>
<React.Suspense
fallback={
<h1 className="display-1 text-secondary p-5 m-auto">
<h1 className="display-1 text-placeholder p-5 m-auto">
<FontAwesomeIcon icon={faSpinner} size="lg" spin />
</h1>
}

View File

@@ -4,9 +4,11 @@ import { storiesOf } from "@storybook/react";
import { AlertStore } from "Stores/AlertStore";
import { Settings } from "Stores/Settings";
import { ThemeContext } from "Components/Theme";
import { ReactSelectColors, ReactSelectStyles } from "Components/MultiSelect";
import { MainModalContent, TabNames } from "./MainModalContent";
import "Percy.scss";
import "Styles/Percy.scss";
storiesOf("MainModal", module)
.addDecorator(storyFn => (
@@ -20,13 +22,19 @@ storiesOf("MainModal", module)
const alertStore = new AlertStore([]);
const settingsStore = new Settings();
return (
<MainModalContent
alertStore={alertStore}
settingsStore={settingsStore}
onHide={() => {}}
isVisible={true}
expandAllOptions={true}
/>
<ThemeContext.Provider
value={{
reactSelectStyles: ReactSelectStyles(ReactSelectColors.Light)
}}
>
<MainModalContent
alertStore={alertStore}
settingsStore={settingsStore}
onHide={() => {}}
isVisible={true}
expandAllOptions={true}
/>
</ThemeContext.Provider>
);
})
.add("Help", () => {

View File

@@ -4,6 +4,8 @@ import { mount } from "enzyme";
import { AlertStore } from "Stores/AlertStore";
import { Settings } from "Stores/Settings";
import { ThemeContext } from "Components/Theme";
import { ReactSelectColors, ReactSelectStyles } from "Components/MultiSelect";
import { MainModal } from ".";
let alertStore;
@@ -20,7 +22,13 @@ beforeEach(() => {
const MountedMainModal = () => {
return mount(
<MainModal alertStore={alertStore} settingsStore={settingsStore} />
<ThemeContext.Provider
value={{
reactSelectStyles: ReactSelectStyles(ReactSelectColors.Light)
}}
>
<MainModal alertStore={alertStore} settingsStore={settingsStore} />
</ThemeContext.Provider>
);
};

View File

@@ -245,7 +245,7 @@ const DeleteSilenceModalContent = observer(
<div className="d-flex flex-row-reverse">
<button
type="button"
className="btn btn-outline-danger mr-2"
className="btn btn-danger mr-2"
onClick={this.onDelete}
disabled={
this.deleteState.fetch !== null &&
@@ -299,7 +299,7 @@ const DeleteSilence = observer(
return (
<React.Fragment>
<button
className="btn btn-outline-danger btn-sm"
className="btn btn-danger btn-sm"
onClick={this.toggle.toggle}
>
<FontAwesomeIcon

View File

@@ -86,7 +86,7 @@ const VerifyResponse = async response => {
await expect(tree.instance().previewState.fetch).resolves.toBeUndefined();
fetch.mockResponseOnce(JSON.stringify(response));
tree.find(".btn-outline-danger").simulate("click");
tree.find(".btn-danger").simulate("click");
await expect(tree.instance().deleteState.fetch).resolves.toBeUndefined();
return tree;
@@ -211,7 +211,7 @@ describe("<DeleteSilenceModalContent />", () => {
expect(fetch.mock.calls[1][1]).toMatchObject({ method: "DELETE" });
expect(fetch.mock.calls).toHaveLength(2);
tree.find(".btn-outline-danger").simulate("click");
tree.find(".btn-danger").simulate("click");
expect(fetch.mock.calls).toHaveLength(2);
tree.instance().onDelete();
expect(fetch.mock.calls).toHaveLength(2);
@@ -249,7 +249,7 @@ describe("<DeleteSilenceModalContent />", () => {
fetch.resetMocks();
fetch.mockReject("Fetch error");
tree.find(".btn-outline-danger").simulate("click");
tree.find(".btn-danger").simulate("click");
await expect(tree.instance().deleteState.fetch).resolves.toBeUndefined();
tree.update();
@@ -265,7 +265,7 @@ describe("<DeleteSilenceModalContent />", () => {
fetch.resetMocks();
fetch.mockResponseOnce("500 Internal Server Error", { status: 500 });
tree.find(".btn-outline-danger").simulate("click");
tree.find(".btn-danger").simulate("click");
await expect(tree.instance().deleteState.fetch).resolves.toBeUndefined();
tree.update();

View File

@@ -153,7 +153,7 @@ const SilenceDetails = ({
<div className="flex-shrink-0 flex-grow-0 mt-lg-0 mt-2 ml-lg-2 ml-0">
<div className="d-flex flex-fill flex-lg-column flex-row justify-content-around">
<button
className="btn btn-outline-secondary btn-sm mb-lg-2 mb-0"
className="btn btn-primary btn-sm mb-lg-2 mb-0"
onClick={onEditSilence}
>
<FontAwesomeIcon

View File

@@ -76,7 +76,7 @@ const SilenceProgress = observer(
return (
<span className="badge badge-light nmb-05 align-text-bottom p-1">
Expires <Moment fromNow>{silence.endsAt}</Moment>
<div className="progress silence-progress bg-white">
<div className="progress silence-progress">
<div
className={progressClass}
role="progressbar"

View File

@@ -1,8 +1,8 @@
.silence-progress.progress {
.badge > .silence-progress.progress {
height: 2px;
margin-top: 0.125rem;
}
.silence-progress.progress > .progress-bar {
.badge > .silence-progress.progress > .progress-bar {
height: 2px;
}

View File

@@ -34,7 +34,7 @@ exports[`<ManagedSilence /> matches snapshot when collapsed 1`] = `
<time datetime=\\"946688400000\\">
in 30 minutes
</time>
<div class=\\"progress silence-progress bg-white\\">
<div class=\\"progress silence-progress\\">
<div class=\\"progress-bar bg-success\\"
role=\\"progressbar\\"
style=\\"width: 50%;\\"
@@ -313,7 +313,7 @@ exports[`<ManagedSilence /> matches snapshot with expaned details 1`] = `
</div>
<div class=\\"flex-shrink-0 flex-grow-0 mt-lg-0 mt-2 ml-lg-2 ml-0\\">
<div class=\\"d-flex flex-fill flex-lg-column flex-row justify-content-around\\">
<button class=\\"btn btn-outline-secondary btn-sm mb-lg-2 mb-0\\">
<button class=\\"btn btn-primary btn-sm mb-lg-2 mb-0\\">
<svg aria-hidden=\\"true\\"
focusable=\\"false\\"
data-prefix=\\"fas\\"
@@ -330,7 +330,7 @@ exports[`<ManagedSilence /> matches snapshot with expaned details 1`] = `
</svg>
Edit
</button>
<button class=\\"btn btn-outline-danger btn-sm\\">
<button class=\\"btn btn-danger btn-sm\\">
<svg aria-hidden=\\"true\\"
focusable=\\"false\\"
data-prefix=\\"fas\\"

View File

@@ -11,8 +11,6 @@ import { MountFade } from "Components/Animations/MountFade";
import { SilenceComment } from "./SilenceComment";
import { SilenceDetails } from "./SilenceDetails";
import "./index.scss";
const ManagedSilence = observer(
class ManagedSilence extends Component {
static propTypes = {

View File

@@ -7,7 +7,7 @@ import { AlertStore } from "Stores/AlertStore";
import { SilenceFormStore } from "Stores/SilenceFormStore";
import { ManagedSilence } from ".";
import "Percy.scss";
import "Styles/Percy.scss";
storiesOf("ManagedSilence", module)
.addDecorator(storyFn => (

View File

@@ -106,7 +106,7 @@ describe("<ManagedSilence />", () => {
tree.instance().collapse.toggle();
tree.update();
const button = tree.find(".btn-outline-secondary");
const button = tree.find(".btn-primary");
expect(button.text()).toBe("Edit");
});
@@ -115,7 +115,7 @@ describe("<ManagedSilence />", () => {
tree.instance().collapse.toggle();
tree.update();
const button = tree.find(".btn-outline-danger");
const button = tree.find(".btn-danger");
expect(button.text()).toBe("Delete");
});
@@ -125,7 +125,7 @@ describe("<ManagedSilence />", () => {
tree.instance().collapse.toggle();
tree.update();
const button = tree.find(".btn-outline-secondary");
const button = tree.find(".btn-primary");
expect(button.text()).toBe("Recreate");
});
@@ -136,7 +136,7 @@ describe("<ManagedSilence />", () => {
expect(silenceFormStore.data.silenceID).toBeNull();
const button = tree.find(".btn-outline-secondary");
const button = tree.find(".btn-primary");
expect(button.text()).toBe("Edit");
const fillSpy = jest.spyOn(silenceFormStore.data, "fillFormFromSilence");

View File

@@ -14,8 +14,6 @@ import {
MountModalBackdrop
} from "Components/Animations/MountModal";
import "./index.scss";
const Modal = observer(
class Modal extends Component {
static propTypes = {
@@ -84,7 +82,12 @@ const Modal = observer(
return ReactDOM.createPortal(
<React.Fragment>
<MountModal in={isOpen} unmountOnExit {...props}>
<MountModal
in={isOpen}
unmountOnExit
className="modal-open"
{...props}
>
<HotKeys
innerRef={this.HotKeysRef}
keyMap={{ CLOSE: "Escape" }}

View File

@@ -1,6 +1,57 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<CustomMultiSelect /> matches snapshot when focused 1`] = `
exports[`<MultiSelect /> matches snapshot without any extra props 1`] = `
"
<div class=\\" css-2b097c-container\\">
<div class=\\"react-select__control css-r5n82u-control\\">
<div class=\\"react-select__value-container css-97xgis\\">
<div class=\\"react-select__placeholder css-1wa3eu0-placeholder\\">
Select...
</div>
<div class=\\"css-b8ldur-Input\\">
<div class=\\"react-select__input\\"
style=\\"display: inline-block;\\"
>
<input autocapitalize=\\"none\\"
autocomplete=\\"off\\"
autocorrect=\\"off\\"
id=\\"react-select-2-input\\"
spellcheck=\\"false\\"
tabindex=\\"0\\"
type=\\"text\\"
aria-autocomplete=\\"list\\"
style=\\"box-sizing: content-box; width: 2px; border: 0px; font-size: inherit; opacity: 1; outline: 0; padding: 0px;\\"
value
>
<div style=\\"position: absolute; top: 0px; left: 0px; visibility: hidden; height: 0px; overflow: scroll; white-space: pre; font-size: inherit; font-family: -webkit-small-control; letter-spacing: normal; text-transform: none;\\">
</div>
</div>
</div>
</div>
<div class=\\"react-select__indicators css-vcwr3k-IndicatorsContainer\\">
<span class=\\"react-select__indicator-separator css-1okebmr-indicatorSeparator\\">
</span>
<div aria-hidden=\\"true\\"
class=\\"react-select__indicator react-select__dropdown-indicator css-tlfecz-indicatorContainer\\"
>
<svg height=\\"20\\"
width=\\"20\\"
viewbox=\\"0 0 20 20\\"
aria-hidden=\\"true\\"
focusable=\\"false\\"
class=\\"css-6q0nyr-Svg\\"
>
<path d=\\"M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z\\">
</path>
</svg>
</div>
</div>
</div>
</div>
"
`;
exports[`<WrappedCustomMultiSelect /> matches snapshot when focused 1`] = `
"
<div class=\\" css-2b097c-container\\">
<span aria-live=\\"polite\\"
@@ -13,8 +64,8 @@ exports[`<CustomMultiSelect /> matches snapshot when focused 1`] = `
&nbsp; 0 results available. Select is focused ,type to refine list, press Down to open the menu,
</p>
</span>
<div class=\\"react-select__control react-select__control--is-focused css-nmfa0p-control\\">
<div class=\\"react-select__value-container css-pb81dw\\">
<div class=\\"react-select__control react-select__control--is-focused css-11rrdhm-control\\">
<div class=\\"react-select__value-container css-97xgis\\">
<div class=\\"react-select__placeholder css-1wa3eu0-placeholder\\">
Select...
</div>
@@ -25,7 +76,7 @@ exports[`<CustomMultiSelect /> matches snapshot when focused 1`] = `
<input autocapitalize=\\"none\\"
autocomplete=\\"off\\"
autocorrect=\\"off\\"
id=\\"react-select-4-input\\"
id=\\"react-select-5-input\\"
spellcheck=\\"false\\"
tabindex=\\"0\\"
type=\\"text\\"
@@ -61,30 +112,30 @@ exports[`<CustomMultiSelect /> matches snapshot when focused 1`] = `
"
`;
exports[`<CustomMultiSelect /> matches snapshot with a value 1`] = `
exports[`<WrappedCustomMultiSelect /> matches snapshot with a value 1`] = `
"
<div class=\\" css-2b097c-container\\">
<div class=\\"react-select__control css-au8pbo-control\\">
<div class=\\"react-select__value-container react-select__value-container--has-value css-pb81dw\\">
<div class=\\"react-select__single-value css-1uccc91-singleValue\\">
<div class=\\"react-select__control css-r5n82u-control\\">
<div class=\\"react-select__value-container react-select__value-container--has-value css-97xgis\\">
<div class=\\"react-select__single-value css-1wh03ml-singleValue\\">
foo
</div>
<div class=\\"css-b8ldur-Input\\">
<div class=\\"react-select__input\\"
style=\\"display:inline-block\\"
style=\\"display: inline-block;\\"
>
<input type=\\"text\\"
autocapitalize=\\"none\\"
<input autocapitalize=\\"none\\"
autocomplete=\\"off\\"
autocorrect=\\"off\\"
id=\\"react-select-5-input\\"
id=\\"react-select-6-input\\"
spellcheck=\\"false\\"
tabindex=\\"0\\"
value
type=\\"text\\"
aria-autocomplete=\\"list\\"
style=\\"box-sizing:content-box;width:1px;label:input;background:0;border:0;font-size:inherit;opacity:1;outline:0;padding:0;color:inherit\\"
style=\\"box-sizing: content-box; width: 2px; border: 0px; font-size: inherit; opacity: 1; outline: 0; padding: 0px;\\"
value
>
<div style=\\"position:absolute;top:0;left:0;visibility:hidden;height:0;overflow:scroll;white-space:pre\\">
<div style=\\"position: absolute; top: 0px; left: 0px; visibility: hidden; height: 0px; overflow: scroll; white-space: pre; font-size: inherit; font-family: -webkit-small-control; letter-spacing: normal; text-transform: none;\\">
</div>
</div>
</div>
@@ -112,30 +163,30 @@ exports[`<CustomMultiSelect /> matches snapshot with a value 1`] = `
"
`;
exports[`<CustomMultiSelect /> matches snapshot with defaults 1`] = `
exports[`<WrappedCustomMultiSelect /> matches snapshot with defaults 1`] = `
"
<div class=\\" css-2b097c-container\\">
<div class=\\"react-select__control css-au8pbo-control\\">
<div class=\\"react-select__value-container css-pb81dw\\">
<div class=\\"react-select__control css-r5n82u-control\\">
<div class=\\"react-select__value-container css-97xgis\\">
<div class=\\"react-select__placeholder css-1wa3eu0-placeholder\\">
Select...
</div>
<div class=\\"css-b8ldur-Input\\">
<div class=\\"react-select__input\\"
style=\\"display:inline-block\\"
style=\\"display: inline-block;\\"
>
<input type=\\"text\\"
autocapitalize=\\"none\\"
<input autocapitalize=\\"none\\"
autocomplete=\\"off\\"
autocorrect=\\"off\\"
id=\\"react-select-2-input\\"
id=\\"react-select-3-input\\"
spellcheck=\\"false\\"
tabindex=\\"0\\"
value
type=\\"text\\"
aria-autocomplete=\\"list\\"
style=\\"box-sizing:content-box;width:1px;label:input;background:0;border:0;font-size:inherit;opacity:1;outline:0;padding:0;color:inherit\\"
style=\\"box-sizing: content-box; width: 2px; border: 0px; font-size: inherit; opacity: 1; outline: 0; padding: 0px;\\"
value
>
<div style=\\"position:absolute;top:0;left:0;visibility:hidden;height:0;overflow:scroll;white-space:pre\\">
<div style=\\"position: absolute; top: 0px; left: 0px; visibility: hidden; height: 0px; overflow: scroll; white-space: pre; font-size: inherit; font-family: -webkit-small-control; letter-spacing: normal; text-transform: none;\\">
</div>
</div>
</div>
@@ -163,36 +214,36 @@ exports[`<CustomMultiSelect /> matches snapshot with defaults 1`] = `
"
`;
exports[`<CustomMultiSelect /> matches snapshot with isDisabled=true 1`] = `
exports[`<WrappedCustomMultiSelect /> matches snapshot with isDisabled=true 1`] = `
"
<div class=\\"react-select--is-disabled css-14jk2my-container\\">
<div class=\\"react-select__control react-select__control--is-disabled css-jk9kg2-control\\">
<div class=\\"react-select__value-container react-select__value-container--has-value css-57r8lk\\">
<div class=\\"react-select__single-value react-select__single-value--is-disabled css-107lb6w-singleValue\\">
<div class=\\"react-select__control react-select__control--is-disabled css-r5n82u-control\\">
<div class=\\"react-select__value-container react-select__value-container--has-value css-97xgis\\">
<div class=\\"react-select__single-value react-select__single-value--is-disabled css-1wh03ml-singleValue\\">
foo
</div>
<div class=\\"css-b8ldur-Input\\">
<div class=\\"react-select__input\\"
style=\\"display:inline-block\\"
style=\\"display: inline-block;\\"
>
<input type=\\"text\\"
disabled
<input disabled
autocapitalize=\\"none\\"
autocomplete=\\"off\\"
autocorrect=\\"off\\"
id=\\"react-select-7-input\\"
id=\\"react-select-8-input\\"
spellcheck=\\"false\\"
tabindex=\\"0\\"
value
type=\\"text\\"
aria-autocomplete=\\"list\\"
style=\\"box-sizing:content-box;width:1px;label:input;background:0;border:0;font-size:inherit;opacity:1;outline:0;padding:0;color:inherit\\"
style=\\"box-sizing: content-box; width: 2px; border: 0px; font-size: inherit; opacity: 1; outline: 0; padding: 0px;\\"
value
>
<div style=\\"position:absolute;top:0;left:0;visibility:hidden;height:0;overflow:scroll;white-space:pre\\">
<div style=\\"position: absolute; top: 0px; left: 0px; visibility: hidden; height: 0px; overflow: scroll; white-space: pre; font-size: inherit; font-family: -webkit-small-control; letter-spacing: normal; text-transform: none;\\">
</div>
</div>
</div>
</div>
<div class=\\"react-select__indicators css-15bdmde-IndicatorsContainer\\">
<div class=\\"react-select__indicators css-vcwr3k-IndicatorsContainer\\">
<span class=\\"react-select__indicator-separator css-109onse-indicatorSeparator\\">
</span>
<div aria-hidden=\\"true\\"
@@ -215,30 +266,30 @@ exports[`<CustomMultiSelect /> matches snapshot with isDisabled=true 1`] = `
"
`;
exports[`<CustomMultiSelect /> matches snapshot with isMulti=true 1`] = `
exports[`<WrappedCustomMultiSelect /> matches snapshot with isMulti=true 1`] = `
"
<div class=\\" css-2b097c-container\\">
<div class=\\"react-select__control css-au8pbo-control\\">
<div class=\\"react-select__value-container react-select__value-container--is-multi css-10war8y\\">
<div class=\\"react-select__control css-r5n82u-control\\">
<div class=\\"react-select__value-container react-select__value-container--is-multi css-nj4yyx\\">
<div class=\\"react-select__placeholder css-1wa3eu0-placeholder\\">
Select...
</div>
<div class=\\"css-b8ldur-Input\\">
<div class=\\"react-select__input\\"
style=\\"display:inline-block\\"
style=\\"display: inline-block;\\"
>
<input type=\\"text\\"
autocapitalize=\\"none\\"
<input autocapitalize=\\"none\\"
autocomplete=\\"off\\"
autocorrect=\\"off\\"
id=\\"react-select-3-input\\"
id=\\"react-select-4-input\\"
spellcheck=\\"false\\"
tabindex=\\"0\\"
value
type=\\"text\\"
aria-autocomplete=\\"list\\"
style=\\"box-sizing:content-box;width:1px;label:input;background:0;border:0;font-size:inherit;opacity:1;outline:0;padding:0;color:inherit\\"
style=\\"box-sizing: content-box; width: 2px; border: 0px; font-size: inherit; opacity: 1; outline: 0; padding: 0px;\\"
value
>
<div style=\\"position:absolute;top:0;left:0;visibility:hidden;height:0;overflow:scroll;white-space:pre\\">
<div style=\\"position: absolute; top: 0px; left: 0px; visibility: hidden; height: 0px; overflow: scroll; white-space: pre; font-size: inherit; font-family: -webkit-small-control; letter-spacing: normal; text-transform: none;\\">
</div>
</div>
</div>
@@ -266,11 +317,11 @@ exports[`<CustomMultiSelect /> matches snapshot with isMulti=true 1`] = `
"
`;
exports[`<CustomMultiSelect /> matches snapshot with isMulti=true and a value 1`] = `
exports[`<WrappedCustomMultiSelect /> matches snapshot with isMulti=true and a value 1`] = `
"
<div class=\\" css-2b097c-container\\">
<div class=\\"react-select__control css-au8pbo-control\\">
<div class=\\"react-select__value-container react-select__value-container--is-multi react-select__value-container--has-value css-10war8y\\">
<div class=\\"react-select__control css-r5n82u-control\\">
<div class=\\"react-select__value-container react-select__value-container--is-multi react-select__value-container--has-value css-nj4yyx\\">
<div class=\\"css-owt1hd-multiValue react-select__multi-value\\">
<div class=\\"css-xbn6jz react-select__multi-value__label\\">
foo
@@ -290,20 +341,20 @@ exports[`<CustomMultiSelect /> matches snapshot with isMulti=true and a value 1`
</div>
<div class=\\"css-b8ldur-Input\\">
<div class=\\"react-select__input\\"
style=\\"display:inline-block\\"
style=\\"display: inline-block;\\"
>
<input type=\\"text\\"
autocapitalize=\\"none\\"
<input autocapitalize=\\"none\\"
autocomplete=\\"off\\"
autocorrect=\\"off\\"
id=\\"react-select-6-input\\"
id=\\"react-select-7-input\\"
spellcheck=\\"false\\"
tabindex=\\"0\\"
value
type=\\"text\\"
aria-autocomplete=\\"list\\"
style=\\"box-sizing:content-box;width:1px;label:input;background:0;border:0;font-size:inherit;opacity:1;outline:0;padding:0;color:inherit\\"
style=\\"box-sizing: content-box; width: 2px; border: 0px; font-size: inherit; opacity: 1; outline: 0; padding: 0px;\\"
value
>
<div style=\\"position:absolute;top:0;left:0;visibility:hidden;height:0;overflow:scroll;white-space:pre\\">
<div style=\\"position: absolute; top: 0px; left: 0px; visibility: hidden; height: 0px; overflow: scroll; white-space: pre; font-size: inherit; font-family: -webkit-small-control; letter-spacing: normal; text-transform: none;\\">
</div>
</div>
</div>

View File

@@ -2,32 +2,65 @@ import React from "react";
import Creatable from "react-select/creatable";
const ReactSelectStyles = {
import { ThemeContext } from "Components/Theme";
const ReactSelectColors = {
Light: {
color: "#fff",
singleValueColor: "#000",
backgroundColor: "#fff",
borderColor: "#ced4da",
focusedBoxShadow: "rgba(69, 90, 100, 0.25)",
focusedBorderColor: "#819ba8",
menuBackground: "#fff",
optionHoverBackground: "#455a64",
valueContainerBackground: "#fff",
disabledValueContainerBackground: "#fff"
},
Dark: {
color: "#fff",
singleValueColor: "#fff",
backgroundColor: "#fff",
borderColor: "#444",
focusedBoxShadow: "rgba(69, 90, 100, 0.25)",
focusedBorderColor: "#819ba8",
menuBackground: "#222",
optionHoverBackground: "#455a64",
valueContainerBackground: "#444",
disabledValueContainerBackground: "#fff"
}
};
const ReactSelectStyles = theme => ({
control: (base, state) =>
state.isFocused
? {
...base,
backgroundColor: theme.backgroundColor,
outline: "0",
outlineOffset: "-2px",
boxShadow: "0 0 0 0.2rem rgba(69, 90, 100, 0.25)",
boxShadow: `0 0 0 0.2rem ${theme.focusedBoxShadow}`,
borderRadius: "0.25rem",
borderColor: "#819ba8",
borderColor: theme.focusedBorderColor,
"&:hover": {
borderColor: "#819ba8"
borderColor: theme.focusedBorderColor
}
}
: {
...base,
backgroundColor: "inherit",
borderRadius: "0.25rem",
borderColor: "#ced4da",
"&:hover": { borderColor: "#ced4da" }
borderColor: theme.borderColor,
"&:hover": { borderColor: theme.borderColor }
},
valueContainer: (base, state) =>
state.isMulti
? {
...base,
borderRadius: "0.25rem",
backgroundColor: state.isDisabled ? "#ecf0f1" : "#fff",
borderRadius: 0,
backgroundColor: state.isDisabled
? theme.disabledValueContainerBackground
: theme.valueContainerBackground,
paddingLeft: "4px",
paddingRight: "4px",
display: "flex",
@@ -38,57 +71,70 @@ const ReactSelectStyles = {
}
: {
...base,
borderRadius: "0.25rem",
backgroundColor: state.isDisabled ? "#ecf0f1" : "#fff"
borderRadius: 0,
backgroundColor: state.isDisabled
? theme.disabledValueContainerBackground
: theme.valueContainerBackground
},
singleValue: (base, state) => ({
...base,
color: theme.singleValueColor
}),
multiValue: (base, state) => ({
...base,
borderRadius: "4px",
backgroundColor: "#455a64",
backgroundColor: theme.optionHoverBackground,
"&:hover": {
backgroundColor: "#455a64"
backgroundColor: theme.optionHoverBackground
}
}),
multiValueLabel: (base, state) => ({
...base,
color: "#fff",
color: theme.color,
whiteSpace: "normal",
wordWrap: "break-word",
wordBreak: "break-word",
"&:hover": {
color: "#fff"
color: theme.color
}
}),
multiValueRemove: (base, state) => ({
...base,
cursor: "pointer",
color: "#fff",
color: theme.color,
backgroundColor: "inherit",
opacity: "0.4",
borderRadius: "inherit",
"&:hover": {
color: "#fff",
color: theme.color,
backgroundColor: "inherit",
opacity: "0.75"
}
}),
indicatorsContainer: (base, state) => ({
...base,
backgroundColor: state.isDisabled ? "#ecf0f1" : "#fff",
backgroundColor: state.isDisabled
? theme.disabledValueContainerBackground
: theme.valueContainerBackground,
borderTopRightRadius: "0.25rem",
borderBottomRightRadius: "0.25rem"
}),
menu: (base, state) => ({
...base,
zIndex: 1500
zIndex: 1500,
backgroundColor: theme.menuBackground
}),
option: (base, state) => ({
...base,
color: "inherit",
backgroundColor: "inherit",
"&:hover": { color: "#fff", backgroundColor: "#455a64", cursor: "pointer" }
"&:hover": {
color: theme.color,
backgroundColor: theme.optionHoverBackground,
cursor: "pointer"
}
})
};
});
class MultiSelect extends Creatable {
renderProps = () => ({});
@@ -96,12 +142,13 @@ class MultiSelect extends Creatable {
render() {
return (
<Creatable
styles={ReactSelectStyles}
styles={this.context.reactSelectStyles}
classNamePrefix="react-select"
{...this.renderProps()}
/>
);
}
}
MultiSelect.contextType = ThemeContext;
export { MultiSelect, ReactSelectStyles };
export { MultiSelect, ReactSelectStyles, ReactSelectColors };

View File

@@ -1,17 +1,29 @@
import React from "react";
import { shallow, mount } from "enzyme";
import { mount } from "enzyme";
import toDiffableHtml from "diffable-html";
import { ThemeContext } from "Components/Theme";
import { ReactSelectColors, ReactSelectStyles } from "Components/MultiSelect";
import { MultiSelect } from ".";
const Option = value => ({ label: value, value: value });
const Wrapped = component => (
<ThemeContext.Provider
value={{
reactSelectStyles: ReactSelectStyles(ReactSelectColors.Light)
}}
>
{component}
</ThemeContext.Provider>
);
describe("<MultiSelect />", () => {
it("renders without any extra props", () => {
const tree = shallow(<MultiSelect />);
expect(tree.text()).toBe("<StateManager />");
it("matches snapshot without any extra props", () => {
const tree = mount(Wrapped(<MultiSelect />));
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
});
});
@@ -24,27 +36,30 @@ class CustomMultiSelect extends MultiSelect {
renderProps = () => this.extraProps;
}
describe("<CustomMultiSelect />", () => {
const WrappedCustomMultiSelect = props =>
Wrapped(<CustomMultiSelect {...props} />);
describe("<WrappedCustomMultiSelect />", () => {
it("matches snapshot with defaults", () => {
const tree = shallow(<CustomMultiSelect />);
const tree = mount(<WrappedCustomMultiSelect />);
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
});
it("matches snapshot with isMulti=true", () => {
const tree = shallow(<CustomMultiSelect isMulti />);
const tree = mount(<WrappedCustomMultiSelect isMulti />);
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
});
it("matches snapshot when focused", () => {
// this test is to cover styles state.isFocused conditions
const tree = mount(<CustomMultiSelect autoFocus />);
const tree = mount(<WrappedCustomMultiSelect autoFocus />);
tree.find("input").simulate("focus");
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
});
it("matches snapshot with a value", () => {
const tree = shallow(
<CustomMultiSelect
const tree = mount(
<WrappedCustomMultiSelect
defaultValue={Option("foo")}
options={[Option("foo"), Option("bar")]}
/>
@@ -53,8 +68,8 @@ describe("<CustomMultiSelect />", () => {
});
it("matches snapshot with isMulti=true and a value", () => {
const tree = shallow(
<CustomMultiSelect
const tree = mount(
<WrappedCustomMultiSelect
isMulti
defaultValue={Option("foo")}
options={[Option("foo"), Option("bar")]}
@@ -64,8 +79,8 @@ describe("<CustomMultiSelect />", () => {
});
it("matches snapshot with isDisabled=true", () => {
const tree = shallow(
<CustomMultiSelect
const tree = mount(
<WrappedCustomMultiSelect
isDisabled
defaultValue={Option("foo")}
options={[Option("foo"), Option("bar")]}

View File

@@ -21,13 +21,13 @@ describe("<FetchIndicator />", () => {
it("shows a pause icon when fetching is paused", () => {
alertStore.status.pause();
const tree = MountedFetchIndicator();
expect(tree.html()).toMatch(/fa-pause-circle/);
expect(toDiffableHtml(tree.html())).toMatch(/fa-pause-circle/);
});
it("shows a cirle notch icon when fetching is resumed", () => {
alertStore.status.resume();
const tree = MountedFetchIndicator();
expect(tree.html()).toMatch(/fa-circle-notch/);
expect(toDiffableHtml(tree.html())).toMatch(/fa-circle-notch/);
});
it("opacity is 1 when fetch is in progress", () => {

View File

@@ -22,8 +22,6 @@ import { Settings } from "Stores/Settings";
import { DropdownSlide } from "Components/Animations/DropdownSlide";
import { HistoryLabel } from "Components/Labels/HistoryLabel";
import "./History.css";
const defaultHistory = {
filters: []
};
@@ -41,7 +39,7 @@ function ReduceFilter(filter) {
const ActionButton = ({ color, icon, title, action, afterClick }) => (
<button
className={`component-history-button btn btn-sm btn-outline-${color}`}
className={`component-history-button btn btn-sm btn-${color}`}
onClick={() => {
action();
afterClick();
@@ -240,7 +238,7 @@ const History = observer(
<button
ref={ref}
onClick={this.collapse.toggle}
className="input-group-text border-left-0 border-right-0 border-top-0 rounded-0 bg-transparent text-white cursor-pointer components-navbar-history px-2"
className="input-group-text border-left-0 border-right-0 border-top-0 border-light rounded-0 bg-transparent text-white cursor-pointer components-navbar-history px-2"
type="button"
data-toggle="dropdown"
aria-haspopup="true"

View File

@@ -4,7 +4,7 @@ exports[`<FilterInput /> matches snapshot on default render 1`] = `
"
<div class=\\"input-group w-100 mr-2\\">
<div class=\\"input-group-prepend\\">
<span class=\\"input-group-text px-2 border-left-0 border-right-0 border-top-0 rounded-0 bg-transparent text-white\\">
<span class=\\"input-group-text px-2 border-left-0 border-right-0 border-top-0 border-light rounded-0 bg-transparent text-white\\">
<svg aria-hidden=\\"true\\"
focusable=\\"false\\"
data-prefix=\\"fas\\"
@@ -21,7 +21,7 @@ exports[`<FilterInput /> matches snapshot on default render 1`] = `
</svg>
</span>
</div>
<div class=\\"form-control components-filterinput border-left-0 border-right-0 border-top-0 rounded-0 bg-transparent\\">
<div class=\\"form-control components-filterinput border-left-0 border-right-0 border-top-0 border-light rounded-0 bg-transparent\\">
<div role=\\"combobox\\"
aria-haspopup=\\"listbox\\"
aria-owns=\\"react-autowhatever-1\\"
@@ -45,7 +45,7 @@ exports[`<FilterInput /> matches snapshot on default render 1`] = `
</div>
</div>
<div class=\\"input-group-append bg-transparent\\">
<button class=\\"input-group-text border-left-0 border-right-0 border-top-0 rounded-0 bg-transparent text-white cursor-pointer components-navbar-history px-2\\"
<button class=\\"input-group-text border-left-0 border-right-0 border-top-0 border-light rounded-0 bg-transparent text-white cursor-pointer components-navbar-history px-2\\"
type=\\"button\\"
data-toggle=\\"dropdown\\"
aria-haspopup=\\"true\\"

View File

@@ -20,8 +20,6 @@ import { FilterInputLabel } from "Components/Labels/FilterInputLabel";
import { AutosuggestTheme } from "./Constants";
import { History } from "./History";
import "./index.scss";
const FilterInput = observer(
class FilterInput extends Component {
static propTypes = {
@@ -154,7 +152,7 @@ const FilterInput = observer(
<div className="input-group w-100 mr-2">
<div className="input-group-prepend">
<span
className={`input-group-text px-2 border-left-0 border-right-0 border-top-0 rounded-0 ${
className={`input-group-text px-2 border-left-0 border-right-0 border-top-0 border-light rounded-0 ${
this.inputStore.focused ? "bg-focused" : "bg-transparent"
} text-white`}
>
@@ -162,7 +160,7 @@ const FilterInput = observer(
</span>
</div>
<div
className={`form-control components-filterinput border-left-0 border-right-0 border-top-0 rounded-0 ${
className={`form-control components-filterinput border-left-0 border-right-0 border-top-0 border-light rounded-0 ${
this.inputStore.focused ? "bg-focused" : "bg-transparent"
}`}
onClick={event => {

View File

@@ -19,8 +19,6 @@ import { SilenceModal } from "Components/SilenceModal";
import { FetchIndicator } from "./FetchIndicator";
import { FilterInput } from "./FilterInput";
import "./index.scss";
const DesktopIdleTimeout = 1000 * 60 * 3;
const MobileIdleTimeout = 1000 * 12;

View File

@@ -8,7 +8,7 @@ import { SilenceFormStore } from "Stores/SilenceFormStore";
import { HistoryMenuContent } from "./FilterInput/History";
import { NavBar } from ".";
import "Percy.scss";
import "Styles/Percy.scss";
const NewFilter = (raw, name, matcher, value, applied, isValid, hits) => {
const filter = NewUnappliedFilter(raw);

View File

@@ -96,8 +96,8 @@ const LabelsTable = observer(
);
const NothingToShow = () => (
<div className="jumbotron bg-white">
<h1 className="display-5 text-secondary text-center">
<div className="jumbotron bg-transparent">
<h1 className="display-5 text-placeholder text-center">
No labels to display
</h1>
</div>

View File

@@ -499,8 +499,8 @@ exports[`<OverviewModalContent /> matches snapshot with no labels to show 1`] =
</button>
</div>
<div class=\\"modal-body\\">
<div class=\\"jumbotron bg-white\\">
<h1 class=\\"display-5 text-secondary text-center\\">
<div class=\\"jumbotron bg-transparent\\">
<h1 class=\\"display-5 text-placeholder text-center\\">
No labels to display
</h1>
</div>

View File

@@ -13,8 +13,6 @@ import { AlertStore } from "Stores/AlertStore";
import { TooltipWrapper } from "Components/TooltipWrapper";
import { Modal } from "Components/Modal";
import "./index.scss";
// https://github.com/facebook/react/issues/14603
const OverviewModalContent = React.lazy(() =>
import("./OverviewModalContent").then(module => ({
@@ -65,7 +63,7 @@ const OverviewModal = observer(
>
<React.Suspense
fallback={
<h1 className="display-1 text-secondary p-5 m-auto">
<h1 className="display-1 text-placeholder p-5 m-auto">
<FontAwesomeIcon icon={faSpinner} size="lg" spin />
</h1>
}

View File

@@ -1,8 +0,0 @@
@import "src/Theme.scss";
.navbar-brand {
&:hover,
&:focus {
color: $green !important;
}
}

View File

@@ -6,7 +6,7 @@ import { MockGrid } from "__mocks__/Stories.js";
import { AlertStore } from "Stores/AlertStore";
import { OverviewModalContent } from "./OverviewModalContent";
import "Percy.scss";
import "Styles/Percy.scss";
storiesOf("OverviewModal", module)
.addDecorator(storyFn => (

View File

@@ -3,8 +3,8 @@
exports[`<AlertManagerInput /> matches snapshot 1`] = `
"
<div class=\\" css-2b097c-container\\">
<div class=\\"react-select__control css-au8pbo-control\\">
<div class=\\"react-select__value-container react-select__value-container--is-multi react-select__value-container--has-value css-10war8y\\">
<div class=\\"react-select__control css-r5n82u-control\\">
<div class=\\"react-select__value-container react-select__value-container--is-multi react-select__value-container--has-value css-nj4yyx\\">
<div class=\\"css-owt1hd-multiValue react-select__multi-value\\">
<div class=\\"css-xbn6jz react-select__multi-value__label\\">
am1 | am2
@@ -41,20 +41,20 @@ exports[`<AlertManagerInput /> matches snapshot 1`] = `
</div>
<div class=\\"css-b8ldur-Input\\">
<div class=\\"react-select__input\\"
style=\\"display:inline-block\\"
style=\\"display: inline-block;\\"
>
<input type=\\"text\\"
autocapitalize=\\"none\\"
<input autocapitalize=\\"none\\"
autocomplete=\\"off\\"
autocorrect=\\"off\\"
id=\\"react-select-silence-input-alertmanagers-input\\"
spellcheck=\\"false\\"
tabindex=\\"0\\"
value
type=\\"text\\"
aria-autocomplete=\\"list\\"
style=\\"box-sizing:content-box;width:1px;label:input;background:0;border:0;font-size:inherit;opacity:1;outline:0;padding:0;color:inherit\\"
style=\\"box-sizing: content-box; width: 2px; border: 0px; font-size: inherit; opacity: 1; outline: 0; padding: 0px;\\"
value
>
<div style=\\"position:absolute;top:0;left:0;visibility:hidden;height:0;overflow:scroll;white-space:pre\\">
<div style=\\"position: absolute; top: 0px; left: 0px; visibility: hidden; height: 0px; overflow: scroll; white-space: pre; font-size: inherit; font-family: -webkit-small-control; letter-spacing: normal; text-transform: none;\\">
</div>
</div>
</div>

View File

@@ -11,7 +11,8 @@ import {
SilenceFormStore,
AlertmanagerClustersToOption
} from "Stores/SilenceFormStore";
import { MultiSelect, ReactSelectStyles } from "Components/MultiSelect";
import { ThemeContext } from "Components/Theme";
import { MultiSelect } from "Components/MultiSelect";
import { ValidationError } from "Components/MultiSelect/ValidationError";
const AlertManagerInput = observer(
@@ -20,6 +21,7 @@ const AlertManagerInput = observer(
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired
};
static contextType = ThemeContext;
constructor(props) {
super(props);
@@ -68,7 +70,7 @@ const AlertManagerInput = observer(
return (
<Select
styles={ReactSelectStyles}
styles={this.context.reactSelectStyles}
classNamePrefix="react-select"
instanceId="silence-input-alertmanagers"
defaultValue={silenceFormStore.data.alertmanagers}

View File

@@ -1,11 +1,13 @@
import React from "react";
import { shallow, mount } from "enzyme";
import { mount } from "enzyme";
import toDiffableHtml from "diffable-html";
import { AlertStore } from "Stores/AlertStore";
import { SilenceFormStore } from "Stores/SilenceFormStore";
import { ThemeContext } from "Components/Theme";
import { ReactSelectColors, ReactSelectStyles } from "Components/MultiSelect";
import { AlertManagerInput } from ".";
let alertStore;
@@ -52,21 +54,18 @@ beforeEach(() => {
silenceFormStore = new SilenceFormStore();
});
const ShallowAlertManagerInput = () => {
return shallow(
<AlertManagerInput
alertStore={alertStore}
silenceFormStore={silenceFormStore}
/>
);
};
const MountedAlertManagerInput = () => {
return mount(
<AlertManagerInput
alertStore={alertStore}
silenceFormStore={silenceFormStore}
/>
<ThemeContext.Provider
value={{
reactSelectStyles: ReactSelectStyles(ReactSelectColors.Light)
}}
>
<AlertManagerInput
alertStore={alertStore}
silenceFormStore={silenceFormStore}
/>
</ThemeContext.Provider>
);
};
@@ -84,27 +83,35 @@ const ValidateSuggestions = () => {
describe("<AlertManagerInput />", () => {
it("matches snapshot", () => {
const tree = ShallowAlertManagerInput();
const tree = MountedAlertManagerInput();
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
});
it("doesn't render ValidationError after passed validation", () => {
const tree = ShallowAlertManagerInput();
const tree = MountedAlertManagerInput();
silenceFormStore.data.wasValidated = true;
expect(tree.html()).not.toMatch(/fa-exclamation-circle/);
expect(tree.html()).not.toMatch(/Required/);
expect(toDiffableHtml(tree.html())).not.toMatch(/fa-exclamation-circle/);
expect(toDiffableHtml(tree.html())).not.toMatch(/Required/);
});
it("renders ValidationError after failed validation", () => {
const tree = ShallowAlertManagerInput();
const tree = MountedAlertManagerInput();
tree
.find(".react-select__multi-value__remove")
.at(0)
.simulate("click");
tree
.find(".react-select__multi-value__remove")
.at(0)
.simulate("click");
silenceFormStore.data.alertmanagers = [];
silenceFormStore.data.wasValidated = true;
expect(tree.html()).toMatch(/fa-exclamation-circle/);
expect(tree.html()).toMatch(/Required/);
expect(toDiffableHtml(tree.html())).toMatch(/fa-exclamation-circle/);
expect(toDiffableHtml(tree.html())).toMatch(/Required/);
});
it("all available Alertmanager instances are selected by default", () => {
ShallowAlertManagerInput();
MountedAlertManagerInput();
expect(silenceFormStore.data.alertmanagers).toHaveLength(2);
expect(silenceFormStore.data.alertmanagers).toContainEqual({
label: "am1 | am2",
@@ -118,7 +125,7 @@ describe("<AlertManagerInput />", () => {
it("doesn't override last selected Alertmanager instances on mount", () => {
silenceFormStore.data.alertmanagers = [{ label: "am3", value: ["am3"] }];
ShallowAlertManagerInput();
MountedAlertManagerInput();
expect(silenceFormStore.data.alertmanagers).toHaveLength(1);
expect(silenceFormStore.data.alertmanagers).toContainEqual({
label: "am3",
@@ -152,7 +159,7 @@ describe("<AlertManagerInput />", () => {
});
it("silenceFormStore.data.alertmanagers gets updated from alertStore.data.upstreams.instances on mismatch", () => {
const tree = ShallowAlertManagerInput();
const tree = MountedAlertManagerInput();
alertStore.data.upstreams.clusters = {
amNew: ["amNew"]
};

View File

@@ -39,8 +39,8 @@ FetchError.propTypes = {
const Placeholder = ({ content }) => (
<MountFade in={true}>
<div className="jumbotron bg-white">
<h1 className="display-5 text-secondary text-center">{content}</h1>
<div className="jumbotron bg-transparent">
<h1 className="display-5 text-placeholder text-center">{content}</h1>
</div>
</MountFade>
);
@@ -208,7 +208,7 @@ const Browser = observer(
/>
<button
type="button"
className="btn btn-outline-secondary flex-grow-0 flex-shrink-0"
className="btn btn-primary flex-grow-0 flex-shrink-0"
onClick={() => {
this.dataSource.toggleSortReverse();
this.onDebouncedFetch();

View File

@@ -111,7 +111,7 @@ describe("<Browser />", () => {
);
const tree = MountedBrowser();
const sortOrder = tree.find("button.btn-outline-secondary").at(0);
const sortOrder = tree.find("button.btn-primary").at(0);
expect(sortOrder.text()).toBe("Sort order");
sortOrder.simulate("click");

View File

@@ -12,8 +12,6 @@ import { SilenceFormStore } from "Stores/SilenceFormStore";
import { Duration } from "./Duration";
import { HourMinute } from "./HourMinute";
import "./index.scss";
const OffsetBadge = ({ startDate, endDate, prefixLabel }) => {
const days = endDate.diff(startDate, "days");
const hours = endDate.diff(startDate, "hours") % 24;

View File

@@ -1,29 +0,0 @@
@import "src/Theme.scss";
$datepicker__background-color: $white;
$datepicker__border-color: $gray-300;
$datepicker__highlighted-color: $secondary;
$datepicker__muted-color: $gray-600;
$datepicker__selected-color: $primary;
$datepicker__text-color: $black;
$datepicker__header-color: $black;
$datepicker__navigation-disabled-color: $gray-600;
$datepicker__font-size: $font-size-base;
$datepicker__font-family: $font-family-sans-serif;
$datepicker__border-radius: 0.25rem;
@import "~react-datepicker/src/stylesheets/datepicker.scss";
.react-datepicker__day:not(.react-datepicker__day--disabled) {
border-width: 1px;
border-style: solid;
border-color: transparent;
}
.react-datepicker__day:hover:not(.react-datepicker__day--disabled) {
border-color: $datepicker__border-color;
}
.react-datepicker__today-button {
border-bottom-left-radius: $datepicker__border-radius;
border-bottom-right-radius: $datepicker__border-radius;
}

View File

@@ -3,46 +3,66 @@
exports[`<PayloadPreview /> matches snapshot 1`] = `
"
<div>
<pre class=\\"__json-pretty__\\">
<pre class=\\"__json-pretty__\\"
style=\\"line-height:1.3;color:#66d9ef;background:#272822;overflow:auto;\\"
>
{
&quot;
<span class=\\"__json-key__\\">
<span class=\\"__json-key__\\"
style=\\"color:#f92672;\\"
>
matchers
</span>
&quot;: [],
&quot;
<span class=\\"__json-key__\\">
<span class=\\"__json-key__\\"
style=\\"color:#f92672;\\"
>
startsAt
</span>
&quot;:
<span class=\\"__json-string__\\">
<span class=\\"__json-string__\\"
style=\\"color:#fd971f;\\"
>
&quot;2000-02-01T00:00:00.000Z&quot;
</span>
,
&quot;
<span class=\\"__json-key__\\">
<span class=\\"__json-key__\\"
style=\\"color:#f92672;\\"
>
endsAt
</span>
&quot;:
<span class=\\"__json-string__\\">
<span class=\\"__json-string__\\"
style=\\"color:#fd971f;\\"
>
&quot;2000-02-01T01:00:00.000Z&quot;
</span>
,
&quot;
<span class=\\"__json-key__\\">
<span class=\\"__json-key__\\"
style=\\"color:#f92672;\\"
>
createdBy
</span>
&quot;:
<span class=\\"__json-string__\\">
<span class=\\"__json-string__\\"
style=\\"color:#fd971f;\\"
>
&quot;&quot;
</span>
,
&quot;
<span class=\\"__json-key__\\">
<span class=\\"__json-key__\\"
style=\\"color:#f92672;\\"
>
comment
</span>
&quot;:
<span class=\\"__json-string__\\">
<span class=\\"__json-string__\\"
style=\\"color:#fd971f;\\"
>
&quot;PayloadPreview test&quot;
</span>
}

View File

@@ -4,7 +4,7 @@ import PropTypes from "prop-types";
import { observer } from "mobx-react";
import JSONPretty from "react-json-pretty";
import "react-json-pretty/themes/monikai.css";
import * as theme from "react-json-pretty/dist/monikai";
import { SilenceFormStore } from "Stores/SilenceFormStore";
@@ -19,7 +19,10 @@ const PayloadPreview = observer(
return (
<div className="mt-3">
<JSONPretty json={silenceFormStore.data.toAlertmanagerPayload} />
<JSONPretty
json={silenceFormStore.data.toAlertmanagerPayload}
theme={theme}
/>
</div>
);
}

View File

@@ -170,7 +170,7 @@ const SilenceForm = observer(
<TooltipWrapper title="Add a matcher">
<button
type="button"
className="btn btn-outline-secondary mb-3"
className="btn btn-primary mb-3"
onClick={this.addMore}
>
<FontAwesomeIcon icon={faPlus} />
@@ -206,14 +206,14 @@ const SilenceForm = observer(
{silenceFormStore.data.silenceID === null ? null : (
<button
type="button"
className="btn btn-outline-danger mr-2"
className="btn btn-danger mr-2"
onClick={silenceFormStore.data.resetSilenceID}
>
<FontAwesomeIcon icon={faUndoAlt} className="mr-1" />
Reset
</button>
)}
<button type="submit" className="btn btn-outline-primary">
<button type="submit" className="btn btn-primary">
<FontAwesomeIcon icon={faSearch} className="mr-1" />
Preview
</button>

View File

@@ -1,6 +1,6 @@
import React from "react";
import { mount, shallow } from "enzyme";
import { mount } from "enzyme";
import { AlertStore } from "Stores/AlertStore";
import { Settings } from "Stores/Settings";
@@ -9,6 +9,8 @@ import {
SilenceFormStage,
NewEmptyMatcher
} from "Stores/SilenceFormStore";
import { ThemeContext } from "Components/Theme";
import { ReactSelectColors, ReactSelectStyles } from "Components/MultiSelect";
import { SilenceForm } from "./SilenceForm";
let alertStore;
@@ -25,38 +27,33 @@ beforeEach(() => {
silenceFormStore = new SilenceFormStore();
});
const ShallowSilenceForm = () => {
return shallow(
<SilenceForm
alertStore={alertStore}
silenceFormStore={silenceFormStore}
settingsStore={settingsStore}
previewOpen={false}
/>
);
};
const MountedSilenceForm = () => {
return mount(
<SilenceForm
alertStore={alertStore}
silenceFormStore={silenceFormStore}
settingsStore={settingsStore}
previewOpen={false}
/>
<ThemeContext.Provider
value={{
reactSelectStyles: ReactSelectStyles(ReactSelectColors.Light)
}}
>
<SilenceForm
alertStore={alertStore}
silenceFormStore={silenceFormStore}
settingsStore={settingsStore}
previewOpen={false}
/>
</ThemeContext.Provider>
);
};
describe("<SilenceForm /> matchers", () => {
it("has an empty matcher selects on default render", () => {
const tree = ShallowSilenceForm();
const tree = MountedSilenceForm();
const matchers = tree.find("SilenceMatch");
expect(matchers).toHaveLength(1);
expect(silenceFormStore.data.matchers).toHaveLength(1);
});
it("clicking 'Add more' button adds another matcher", () => {
const tree = ShallowSilenceForm();
const tree = MountedSilenceForm();
const button = tree.find("button[type='button']");
button.simulate("click", { preventDefault: jest.fn() });
const matchers = tree.find("SilenceMatch");
@@ -101,21 +98,22 @@ describe("<SilenceForm /> matchers", () => {
describe("<SilenceForm /> preview", () => {
it("doesn't render PayloadPreview when previewCollapse.hidden is true", () => {
const tree = ShallowSilenceForm();
const tree = MountedSilenceForm();
const instance = tree.instance();
instance.previewCollapse.hidden = true;
expect(tree.find("PayloadPreview")).toHaveLength(0);
});
it("renders PayloadPreview when previewCollapse.hidden is false", () => {
const tree = ShallowSilenceForm();
const tree = MountedSilenceForm();
const instance = tree.instance();
instance.previewCollapse.hidden = false;
tree.update();
expect(tree.find("PayloadPreview")).toHaveLength(1);
});
it("clicking on the toggle icon toggles PayloadPreview", () => {
const tree = ShallowSilenceForm();
const tree = MountedSilenceForm();
const button = tree.find(".btn.cursor-pointer.text-muted");
expect(tree.find("PayloadPreview")).toHaveLength(0);
button.simulate("click");
@@ -178,7 +176,7 @@ describe("<SilenceForm /> inputs", () => {
describe("<SilenceForm />", () => {
it("calling submit doesn't move the form to Preview stage when form is invalid", () => {
const tree = ShallowSilenceForm();
const tree = MountedSilenceForm();
tree.simulate("submit", { preventDefault: jest.fn() });
expect(silenceFormStore.data.currentStage).toBe(SilenceFormStage.UserInput);
});
@@ -191,14 +189,14 @@ describe("<SilenceForm />", () => {
silenceFormStore.data.alertmanagers = [{ label: "am1", value: ["am1"] }];
silenceFormStore.data.author = "me@example.com";
silenceFormStore.data.comment = "fake silence";
const tree = ShallowSilenceForm();
const tree = MountedSilenceForm();
tree.simulate("submit", { preventDefault: jest.fn() });
expect(silenceFormStore.data.currentStage).toBe(SilenceFormStage.Preview);
});
it("calling submit saves author value to the Settings store", () => {
silenceFormStore.data.author = "user@example.com";
const tree = ShallowSilenceForm();
const tree = MountedSilenceForm();
tree.simulate("submit", { preventDefault: jest.fn() });
expect(settingsStore.silenceFormConfig.config.author).toBe(
"user@example.com"
@@ -217,14 +215,14 @@ describe("<SilenceForm /> in edit mode", () => {
it("opening form with silenceID shows reset button", () => {
silenceFormStore.data.silenceID = "12345";
const tree = MountedSilenceForm();
const button = tree.find("button.btn-outline-danger");
const button = tree.find("button.btn-danger");
expect(button).toHaveLength(1);
});
it("clicking on Reset button unsets silenceFormStore.data.silenceID", () => {
silenceFormStore.data.silenceID = "12345";
const tree = MountedSilenceForm();
const button = tree.find("button.btn-outline-danger");
const button = tree.find("button.btn-danger");
button.simulate("click");
expect(silenceFormStore.data.silenceID).toBeNull();
});
@@ -232,15 +230,15 @@ describe("<SilenceForm /> in edit mode", () => {
it("clicking on Reset button hides it", () => {
silenceFormStore.data.silenceID = "12345";
const tree = MountedSilenceForm();
const button = tree.find("button.btn-outline-danger");
const button = tree.find("button.btn-danger");
button.simulate("click");
expect(tree.find("button.btn-outline-danger")).toHaveLength(0);
expect(tree.find("button.btn-danger")).toHaveLength(0);
});
it("clicking on Reset button enables AlertManagerInput", () => {
silenceFormStore.data.silenceID = "12345";
const tree = MountedSilenceForm();
const button = tree.find("button.btn-outline-danger");
const button = tree.find("button.btn-danger");
button.simulate("click");
const select = tree.find("StateManager").at(0);
expect(select.props().isDisabled).toBeFalsy();

View File

@@ -1,10 +1,12 @@
import React from "react";
import { shallow, mount } from "enzyme";
import { mount } from "enzyme";
import toDiffableHtml from "diffable-html";
import { NewEmptyMatcher, MatcherValueToObject } from "Stores/SilenceFormStore";
import { ThemeContext } from "Components/Theme";
import { ReactSelectColors, ReactSelectStyles } from "Components/MultiSelect";
import { LabelNameInput } from "./LabelNameInput";
let matcher;
@@ -30,12 +32,16 @@ afterEach(() => {
jest.restoreAllMocks();
});
const ShallowLabelNameInput = isValid => {
return shallow(<LabelNameInput matcher={matcher} isValid={isValid} />);
};
const MountedLabelNameInput = isValid => {
return mount(<LabelNameInput matcher={matcher} isValid={isValid} />);
return mount(
<ThemeContext.Provider
value={{
reactSelectStyles: ReactSelectStyles(ReactSelectColors.Light)
}}
>
<LabelNameInput matcher={matcher} isValid={isValid} />
</ThemeContext.Provider>
);
};
const ValidateSuggestions = () => {
@@ -48,26 +54,26 @@ const ValidateSuggestions = () => {
describe("<LabelNameInput />", () => {
it("matches snapshot", () => {
const tree = ShallowLabelNameInput(true);
const tree = MountedLabelNameInput(true);
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
});
it("doesn't renders ValidationError after passed validation", () => {
// clear the name so placeholder is rendered
matcher.name = "";
const tree = ShallowLabelNameInput(true);
expect(tree.html()).toMatch(/Label name/);
expect(tree.html()).not.toMatch(/fa-exclamation-circle/);
expect(tree.html()).not.toMatch(/Required/);
const tree = MountedLabelNameInput(true);
expect(toDiffableHtml(tree.html())).toMatch(/Label name/);
expect(toDiffableHtml(tree.html())).not.toMatch(/fa-exclamation-circle/);
expect(toDiffableHtml(tree.html())).not.toMatch(/Required/);
});
it("renders ValidationError after failed validation", () => {
// clear the name so placeholder is rendered
matcher.name = "";
const tree = ShallowLabelNameInput(false);
expect(tree.html()).not.toMatch(/Label name/);
expect(tree.html()).toMatch(/fa-exclamation-circle/);
expect(tree.html()).toMatch(/Required/);
const tree = MountedLabelNameInput(false);
expect(toDiffableHtml(tree.html())).not.toMatch(/Label name/);
expect(toDiffableHtml(tree.html())).toMatch(/fa-exclamation-circle/);
expect(toDiffableHtml(tree.html())).toMatch(/Required/);
});
it("renders suggestions", () => {
@@ -89,7 +95,7 @@ describe("<LabelNameInput />", () => {
fetch
.once(JSON.stringify(["name1", "name2", "name3"]))
.once(JSON.stringify(["value1", "value2", "value3"]));
const tree = ShallowLabelNameInput(true);
const tree = MountedLabelNameInput(true);
const instance = tree.instance();
await expect(instance.nameSuggestionsFetch).resolves.toBeUndefined();
await expect(instance.valueSuggestionsFetch).resolves.toBeUndefined();
@@ -106,7 +112,7 @@ describe("<LabelNameInput />", () => {
it("handles fetch errors when populating suggestions", async () => {
fetch.mockReject("error");
const tree = ShallowLabelNameInput(true);
const tree = MountedLabelNameInput(true);
const instance = tree.instance();
await expect(instance.nameSuggestionsFetch).resolves.toBeUndefined();
await expect(instance.valueSuggestionsFetch).resolves.toBeUndefined();
@@ -116,7 +122,7 @@ describe("<LabelNameInput />", () => {
it("handles invalid JSON when populating suggestions", async () => {
jest.spyOn(console, "error").mockImplementation(() => {});
fetch.mockResponse("this is not JSON");
const tree = ShallowLabelNameInput(true);
const tree = MountedLabelNameInput(true);
const instance = tree.instance();
await expect(instance.nameSuggestionsFetch).resolves.toBeUndefined();
await expect(instance.valueSuggestionsFetch).resolves.toBeUndefined();
@@ -126,7 +132,7 @@ describe("<LabelNameInput />", () => {
it("suggestions are emptied on failed fetch", async () => {
fetch.mockReject(new Error("fake error message"));
const tree = ShallowLabelNameInput(true);
const tree = MountedLabelNameInput(true);
const instance = tree.instance();
await expect(instance.nameSuggestionsFetch).resolves.toBeUndefined();
await expect(instance.valueSuggestionsFetch).resolves.toBeUndefined();

View File

@@ -1,6 +1,6 @@
import React from "react";
import { shallow, mount } from "enzyme";
import { mount } from "enzyme";
import toDiffableHtml from "diffable-html";
@@ -9,6 +9,8 @@ import {
NewEmptyMatcher,
MatcherValueToObject
} from "Stores/SilenceFormStore";
import { ThemeContext } from "Components/Theme";
import { ReactSelectColors, ReactSelectStyles } from "Components/MultiSelect";
import { LabelValueInput } from "./LabelValueInput";
let silenceFormStore;
@@ -36,23 +38,19 @@ afterEach(() => {
jest.restoreAllMocks();
});
const ShallowLabelValueInput = isValid => {
return shallow(
<LabelValueInput
silenceFormStore={silenceFormStore}
matcher={matcher}
isValid={isValid}
/>
);
};
const MountedLabelValueInput = isValid => {
return mount(
<LabelValueInput
silenceFormStore={silenceFormStore}
matcher={matcher}
isValid={isValid}
/>
<ThemeContext.Provider
value={{
reactSelectStyles: ReactSelectStyles(ReactSelectColors.Light)
}}
>
<LabelValueInput
silenceFormStore={silenceFormStore}
matcher={matcher}
isValid={isValid}
/>
</ThemeContext.Provider>
);
};
@@ -66,20 +64,20 @@ const ValidateSuggestions = () => {
describe("<LabelValueInput />", () => {
it("matches snapshot", () => {
const tree = ShallowLabelValueInput(true);
const tree = MountedLabelValueInput(true);
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
});
it("doesn't renders ValidationError after passed validation", () => {
const tree = ShallowLabelValueInput(true);
expect(tree.html()).not.toMatch(/fa-exclamation-circle/);
expect(tree.html()).not.toMatch(/Required/);
const tree = MountedLabelValueInput(true);
expect(toDiffableHtml(tree.html())).not.toMatch(/fa-exclamation-circle/);
expect(toDiffableHtml(tree.html())).not.toMatch(/Required/);
});
it("renders ValidationError after failed validation", () => {
const tree = ShallowLabelValueInput(false);
expect(tree.html()).toMatch(/fa-exclamation-circle/);
expect(tree.html()).toMatch(/Required/);
const tree = MountedLabelValueInput(false);
expect(toDiffableHtml(tree.html())).toMatch(/fa-exclamation-circle/);
expect(toDiffableHtml(tree.html())).toMatch(/Required/);
});
it("renders suggestions", () => {

View File

@@ -3,27 +3,27 @@
exports[`<LabelNameInput /> matches snapshot 1`] = `
"
<div class=\\" css-2b097c-container\\">
<div class=\\"react-select__control css-au8pbo-control\\">
<div class=\\"react-select__value-container react-select__value-container--has-value css-pb81dw\\">
<div class=\\"react-select__single-value css-1uccc91-singleValue\\">
<div class=\\"react-select__control css-r5n82u-control\\">
<div class=\\"react-select__value-container react-select__value-container--has-value css-97xgis\\">
<div class=\\"react-select__single-value css-1wh03ml-singleValue\\">
name
</div>
<div class=\\"css-b8ldur-Input\\">
<div class=\\"react-select__input\\"
style=\\"display:inline-block\\"
style=\\"display: inline-block;\\"
>
<input type=\\"text\\"
autocapitalize=\\"none\\"
<input autocapitalize=\\"none\\"
autocomplete=\\"off\\"
autocorrect=\\"off\\"
id=\\"react-select-silence-input-label-name-1-input\\"
spellcheck=\\"false\\"
tabindex=\\"0\\"
value
type=\\"text\\"
aria-autocomplete=\\"list\\"
style=\\"box-sizing:content-box;width:1px;label:input;background:0;border:0;font-size:inherit;opacity:1;outline:0;padding:0;color:inherit\\"
style=\\"box-sizing: content-box; width: 2px; border: 0px; font-size: inherit; opacity: 1; outline: 0; padding: 0px;\\"
value
>
<div style=\\"position:absolute;top:0;left:0;visibility:hidden;height:0;overflow:scroll;white-space:pre\\">
<div style=\\"position: absolute; top: 0px; left: 0px; visibility: hidden; height: 0px; overflow: scroll; white-space: pre; font-size: inherit; font-family: -webkit-small-control; letter-spacing: normal; text-transform: none;\\">
</div>
</div>
</div>

View File

@@ -3,29 +3,18 @@
exports[`<LabelValueInput /> matches snapshot 1`] = `
"
<div class=\\" css-2b097c-container\\">
<div class=\\"react-select__control css-au8pbo-control\\">
<div class=\\"react-select__value-container react-select__value-container--is-multi css-10war8y\\">
<div title=\\"Number of alerts matching this label\\"
class
style=\\"display:inline-block;max-width:100%\\"
<div class=\\"react-select__control css-r5n82u-control\\">
<div class=\\"react-select__value-container react-select__value-container--is-multi css-nj4yyx\\">
<div class
style=\\"display: inline-block; max-width: 100%;\\"
data-tooltipped
aria-describedby=\\"tippy-tooltip-1\\"
data-original-title=\\"Number of alerts matching this label\\"
>
<span class=\\"badge badge-light badge-pill d-block\\"
style=\\"font-size:85%;line-height:1rem\\"
style=\\"font-size: 85%; line-height: 1rem;\\"
>
<svg aria-hidden=\\"true\\"
focusable=\\"false\\"
data-prefix=\\"fas\\"
data-icon=\\"spinner\\"
class=\\"svg-inline--fa fa-spinner fa-w-16 fa-spin \\"
role=\\"img\\"
xmlns=\\"http://www.w3.org/2000/svg\\"
viewbox=\\"0 0 512 512\\"
>
<path fill=\\"currentColor\\"
d=\\"M304 48c0 26.51-21.49 48-48 48s-48-21.49-48-48 21.49-48 48-48 48 21.49 48 48zm-48 368c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48-21.49-48-48-48zm208-208c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48-21.49-48-48-48zM96 256c0-26.51-21.49-48-48-48S0 229.49 0 256s21.49 48 48 48 48-21.49 48-48zm12.922 99.078c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48c0-26.509-21.491-48-48-48zm294.156 0c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48c0-26.509-21.49-48-48-48zM108.922 60.922c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48-21.491-48-48-48z\\"
>
</path>
</svg>
0
</span>
</div>
<div>
@@ -35,20 +24,20 @@ exports[`<LabelValueInput /> matches snapshot 1`] = `
</div>
<div class=\\"css-b8ldur-Input\\">
<div class=\\"react-select__input\\"
style=\\"display:inline-block\\"
style=\\"display: inline-block;\\"
>
<input type=\\"text\\"
autocapitalize=\\"none\\"
<input autocapitalize=\\"none\\"
autocomplete=\\"off\\"
autocorrect=\\"off\\"
id=\\"react-select-silence-input-label-value-1-input\\"
spellcheck=\\"false\\"
tabindex=\\"0\\"
value
type=\\"text\\"
aria-autocomplete=\\"list\\"
style=\\"box-sizing:content-box;width:1px;label:input;background:0;border:0;font-size:inherit;opacity:1;outline:0;padding:0;color:inherit\\"
style=\\"box-sizing: content-box; width: 2px; border: 0px; font-size: inherit; opacity: 1; outline: 0; padding: 0px;\\"
value
>
<div style=\\"position:absolute;top:0;left:0;visibility:hidden;height:0;overflow:scroll;white-space:pre\\">
<div style=\\"position: absolute; top: 0px; left: 0px; visibility: hidden; height: 0px; overflow: scroll; white-space: pre; font-size: inherit; font-family: -webkit-small-control; letter-spacing: normal; text-transform: none;\\">
</div>
</div>
</div>

View File

@@ -75,7 +75,7 @@ const SilenceMatch = observer(
<TooltipWrapper title="Remove this matcher">
<button
type="button"
className="btn btn-outline-danger"
className="btn btn-danger"
onClick={onDelete}
>
<FontAwesomeIcon icon={faTrash} />

View File

@@ -16,8 +16,6 @@ import { SilencePreview } from "./SilencePreview";
import { SilenceSubmitController } from "./SilenceSubmit/SilenceSubmitController";
import { Browser } from "./Browser";
import "./index.css";
const SilenceModalContent = observer(
class SilenceModalContent extends Component {
static propTypes = {

View File

@@ -93,7 +93,7 @@ exports[`<SilencePreview /> matches snapshot 1`] = `
</div>
<div class=\\"d-flex flex-row-reverse\\">
<button type=\\"button\\"
class=\\"btn btn-outline-primary\\"
class=\\"btn btn-primary\\"
>
<svg aria-hidden=\\"true\\"
focusable=\\"false\\"
@@ -112,7 +112,7 @@ exports[`<SilencePreview /> matches snapshot 1`] = `
Submit
</button>
<button type=\\"button\\"
class=\\"btn btn-outline-secondary mr-2\\"
class=\\"btn btn-danger mr-2\\"
>
<svg aria-hidden=\\"true\\"
focusable=\\"false\\"

View File

@@ -32,8 +32,8 @@ FetchError.propTypes = {
};
const Placeholder = () => (
<div className="jumbotron bg-white">
<h1 className="display-5 text-secondary text-center">
<div className="jumbotron bg-transparent">
<h1 className="display-5 text-placeholder text-center">
<FontAwesomeIcon icon={faSpinner} size="lg" spin />
</h1>
</div>
@@ -124,7 +124,7 @@ const SilencePreview = observer(
<div className="d-flex flex-row-reverse">
<button
type="button"
className="btn btn-outline-primary"
className="btn btn-primary"
onClick={silenceFormStore.data.setStageSubmit}
>
<FontAwesomeIcon icon={faCheckCircle} className="pr-1" />
@@ -132,7 +132,7 @@ const SilencePreview = observer(
</button>
<button
type="button"
className="btn btn-outline-secondary mr-2"
className="btn btn-danger mr-2"
onClick={silenceFormStore.data.resetProgress}
>
<FontAwesomeIcon icon={faArrowLeft} className="pr-1" />

View File

@@ -151,7 +151,7 @@ describe("<SilencePreview />", () => {
fetch.mockResponse(JSON.stringify(MockAPIResponse()));
const tree = MountedSilencePreview();
const button = tree.find(".btn-outline-primary");
const button = tree.find(".btn-primary");
button.simulate("click");
expect(silenceFormStore.data.currentStage).toBe(SilenceFormStage.Submit);
});

View File

@@ -33,7 +33,7 @@ class SilenceSubmitController extends Component {
<div className="d-flex flex-row-reverse">
<button
type="button"
className="btn btn-outline-primary"
className="btn btn-primary"
onClick={silenceFormStore.data.resetProgress}
>
<FontAwesomeIcon icon={faArrowLeft} className="pr-1" />

View File

@@ -65,7 +65,7 @@ const SilenceModal = observer(
>
<React.Suspense
fallback={
<h1 className="display-1 text-secondary p-5 m-auto">
<h1 className="display-1 text-placeholder p-5 m-auto">
<FontAwesomeIcon icon={faSpinner} size="lg" spin />
</h1>
}

View File

@@ -13,10 +13,12 @@ import {
MatcherValueToObject,
SilenceTabNames
} from "Stores/SilenceFormStore";
import { ThemeContext } from "Components/Theme";
import { ReactSelectColors, ReactSelectStyles } from "Components/MultiSelect";
import { DateTimeSelect, TabNames } from "./DateTimeSelect";
import { SilenceModalContent } from "./SilenceModalContent";
import "Percy.scss";
import "Styles/Percy.scss";
const MockMatcher = (name, values, isRegex) => {
const matcher = NewEmptyMatcher();
@@ -77,32 +79,38 @@ storiesOf("SilenceModal", module)
return (
<React.Fragment>
<Modal>
<SilenceModalContent
alertStore={alertStore}
silenceFormStore={silenceFormStore}
settingsStore={settingsStore}
onHide={() => {}}
previewOpen={true}
onDeleteModalClose={() => {}}
/>
</Modal>
<Modal>
<div className="pt-2">
<DateTimeSelect
<ThemeContext.Provider
value={{
reactSelectStyles: ReactSelectStyles(ReactSelectColors.Light)
}}
>
<Modal>
<SilenceModalContent
alertStore={alertStore}
silenceFormStore={silenceFormStore}
openTab={TabNames.Start}
settingsStore={settingsStore}
onHide={() => {}}
previewOpen={true}
onDeleteModalClose={() => {}}
/>
</div>
</Modal>
<Modal>
<div className="pt-2">
<DateTimeSelect
silenceFormStore={silenceFormStore}
openTab={TabNames.End}
/>
</div>
</Modal>
</Modal>
<Modal>
<div className="pt-2">
<DateTimeSelect
silenceFormStore={silenceFormStore}
openTab={TabNames.Start}
/>
</div>
</Modal>
<Modal>
<div className="pt-2">
<DateTimeSelect
silenceFormStore={silenceFormStore}
openTab={TabNames.End}
/>
</div>
</Modal>
</ThemeContext.Provider>
</React.Fragment>
);
})

View File

@@ -2,6 +2,8 @@ import React from "react";
import { mount } from "enzyme";
import { ThemeContext } from "Components/Theme";
import { ReactSelectColors, ReactSelectStyles } from "Components/MultiSelect";
import { AlertStore } from "Stores/AlertStore";
import { Settings } from "Stores/Settings";
import { SilenceFormStore, SilenceFormStage } from "Stores/SilenceFormStore";
@@ -24,11 +26,17 @@ beforeEach(() => {
const MountedSilenceModal = () => {
return mount(
<SilenceModal
alertStore={alertStore}
silenceFormStore={silenceFormStore}
settingsStore={settingsStore}
/>
<ThemeContext.Provider
value={{
reactSelectStyles: ReactSelectStyles(ReactSelectColors.Light)
}}
>
<SilenceModal
alertStore={alertStore}
silenceFormStore={silenceFormStore}
settingsStore={settingsStore}
/>
</ThemeContext.Provider>
);
};

View File

@@ -0,0 +1,56 @@
import React from "react";
import ReactDOM from "react-dom";
import { observer } from "mobx-react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faSun } from "@fortawesome/free-solid-svg-icons/faSun";
const DarkTheme = React.lazy(() => import("Styles/DarkTheme"));
const LightTheme = React.lazy(() => import("Styles/LightTheme"));
const Placeholder = () => {
return ReactDOM.createPortal(
<div
style={{
zIndex: 2000,
backgroundColor: "#455a64",
position: "fixed",
top: 0,
left: 0,
width: "100vw",
height: "100vh"
}}
>
<FontAwesomeIcon
icon={faSun}
size="lg"
spin
style={{
color: "#5e7a88",
fontSize: "8rem",
position: "fixed",
top: "50%",
left: "50%",
marginRight: "-50%",
transform: "translate(-50%, -50%)"
}}
/>
</div>,
document.body
);
};
const Theme = observer(({ settingsStore }) => (
<React.Suspense fallback={<Placeholder />}>
{settingsStore.themeConfig.config.darkTheme ? (
<DarkTheme />
) : (
<LightTheme />
)}
</React.Suspense>
));
const ThemeContext = React.createContext();
export { Theme, ThemeContext };

View File

@@ -0,0 +1,26 @@
import React from "react";
import { mount } from "enzyme";
import { Settings } from "Stores/Settings";
import { Theme } from ".";
let settingsStore;
beforeEach(() => {
settingsStore = new Settings();
});
describe("<Theme />", () => {
it("renders DarkTheme when settingsStore.themeConfig.config.darkTheme is true", () => {
settingsStore.themeConfig.config.darkTheme = true;
const tree = mount(<Theme settingsStore={settingsStore} />);
expect(tree.text()).toBe("");
});
it("renders LightTheme when settingsStore.themeConfig.config.darkTheme is false", () => {
settingsStore.themeConfig.config.darkTheme = false;
const tree = mount(<Theme settingsStore={settingsStore} />);
expect(tree.text()).toBe("");
});
});

View File

@@ -1,155 +0,0 @@
@import "src/Theme.scss";
$alertgroup-body-bg: #5a6e7b;
$alertgroup-header-bg: #515658;
$alertgroup-footer-bg: #728998;
$alertgroup-border-inside: #485862;
$silence-bg: #c3cdd5;
$silence-progress-bg: #eaeef0;
$badge-light-bg: #d2dae0;
.dark-theme .card.bg-light {
border-color: #3c3e3e !important;
}
.dark-theme .components-grid-alertgrid-card {
&.card-body,
& > .list-group,
& > .list-group > .list-group-item {
background-color: $alertgroup-body-bg !important;
}
}
.dark-theme .card.bg-light > .card-header {
background-color: $alertgroup-header-bg !important;
}
.dark-theme .card > .card-header {
border-bottom-color: $alertgroup-border-inside !important;
}
.dark-theme .bg-card-footer-default {
background-color: $alertgroup-footer-bg !important;
border-top-color: $alertgroup-border-inside !important;
}
.dark-theme .btn.bg-white {
background-color: $alertgroup-body-bg !important;
}
.dark-theme {
& .components-grid-annotation .text-muted,
& .components-managed-silence-cite .text-muted,
& .components-managed-silence svg.text-muted,
& .text-muted.fa-bell-slash {
color: $dark !important;
}
& .text-muted {
color: $white !important;
}
}
.dark-theme .components-managed-silence {
border-left-color: $dark;
& > .card-header,
& > .card-body {
background-color: $silence-bg !important;
}
}
.dark-theme {
& .modal-content {
background-color: $alertgroup-body-bg;
& .list-group-item {
background-color: $alertgroup-body-bg;
}
}
& .modal-header {
border-bottom-color: $alertgroup-border-inside;
}
& .modal-footer {
border-top-color: $alertgroup-border-inside;
}
& .components-grid-annotation.bg-light {
background-color: $silence-bg !important;
}
& .badge.badge-light {
background-color: $badge-light-bg !important;
}
& .components-filteredinputlabel > .badge-pill.badge-light {
background-color: $badge-light-bg !important;
filter: none;
}
& .dropdown-menu {
background-color: $silence-bg !important;
}
& .dropdown-header {
color: $dark !important;
}
& .progress.bg-white {
background-color: $silence-progress-bg !important;
}
}
.dark-theme {
& .react-select__control,
& .react-select__indicators,
& .react-select__value-container {
background-color: $alertgroup-body-bg !important;
}
& .react-select__menu {
background-color: $silence-bg !important;
}
& .tab-content,
& .tab-content .text-muted,
& .nav-item:not(.components-tab-inactive),
& .custom-control-label,
& .react-select__placeholder {
color: $white !important;
}
& .nav-link.active {
color: $black !important;
background-color: $silence-bg !important;
}
& .form-control {
color: $gray-200 !important;
}
& .react-datepicker,
& .react-datepicker__header,
& .react-datepicker__today-button {
background-color: $silence-bg !important;
}
}
.dark-theme {
& .jumbotron.bg-white {
background-color: $alertgroup-body-bg !important;
}
}
.dark-theme {
& .collapse,
& .collapse > .card-body {
color: $white;
background-color: $alertgroup-body-bg !important;
}
& .Collapsible > .card-header {
background-color: $alertgroup-footer-bg !important;
border-bottom-color: $alertgroup-border-inside !important;
}
& .input-range__label,
& .Collapsible__trigger.bg-light {
color: $white;
}
& .Collapsible__trigger.is-closed {
color: $black;
}
}

View File

@@ -1,5 +0,0 @@
@import "src/App.scss";
body {
font-family: sans-serif !important;
}

Some files were not shown because too many files have changed in this diff Show More