diff --git a/ui/src/Components/Labels/BaseLabel/index.js b/ui/src/Components/Labels/BaseLabel/index.js deleted file mode 100644 index 466047d9c..000000000 --- a/ui/src/Components/Labels/BaseLabel/index.js +++ /dev/null @@ -1,90 +0,0 @@ -import { Component } from "react"; -import PropTypes from "prop-types"; - -import { AlertStore } from "Stores/AlertStore"; -import { - StaticColorLabelClassMap, - DefaultLabelClassMap, - AlertNameLabelClassMap, - StateLabelClassMap, -} from "Common/Colors"; -import { QueryOperators, FormatQuery, StaticLabels } from "Common/Query"; - -const isBackgroundDark = (brightness) => brightness <= 125; - -// base class for shared code, not used directly -class BaseLabel extends Component { - static propTypes = { - alertStore: PropTypes.instanceOf(AlertStore).isRequired, - name: PropTypes.string.isRequired, - value: PropTypes.string.isRequired, - }; - - getClassAndStyle(name, value, extraClass, baseClass) { - const { alertStore } = this.props; - - const elementType = baseClass || "badge"; - - const data = { - style: {}, - className: "", - baseClassNames: ["components-label", elementType], - colorClassNames: [], - }; - - if (name === StaticLabels.AlertName) { - data.colorClassNames.push(AlertNameLabelClassMap[elementType]); - } else if (name === StaticLabels.State) { - data.colorClassNames.push( - StateLabelClassMap[value] - ? `${elementType}-${StateLabelClassMap[value]}` - : DefaultLabelClassMap[elementType] - ); - } else if (alertStore.settings.values.staticColorLabels.includes(name)) { - data.colorClassNames.push(StaticColorLabelClassMap[elementType]); - } else { - const c = alertStore.data.getColorData(name, value); - if (c) { - // if there's color information use it - data.style["backgroundColor"] = `rgba(${[ - c.background.red, - c.background.green, - c.background.blue, - c.background.alpha, - ].join(", ")})`; - - data.colorClassNames.push( - isBackgroundDark(c.brightness) - ? "components-label-dark" - : "components-label-bright" - ); - - data.colorClassNames.push( - `components-label-brightness-${Math.round(c.brightness / 25)}` - ); - } else { - // if not fall back to class - data.colorClassNames.push(DefaultLabelClassMap[elementType]); - } - } - data.className = `${[...data.baseClassNames, ...data.colorClassNames].join( - " " - )} ${extraClass || ""}`; - - return data; - } - - handleClick = (event) => { - // left click => apply foo=bar filter - // left click + alt => apply foo!=bar filter - const operator = - event.altKey === true ? QueryOperators.NotEqual : QueryOperators.Equal; - - event.preventDefault(); - - const { name, value, alertStore } = this.props; - alertStore.filters.addFilter(FormatQuery(name, operator, value)); - }; -} - -export { BaseLabel }; diff --git a/ui/src/Components/Labels/BaseLabel/index.test.js b/ui/src/Components/Labels/BaseLabel/index.test.js deleted file mode 100644 index 6144adddf..000000000 --- a/ui/src/Components/Labels/BaseLabel/index.test.js +++ /dev/null @@ -1,114 +0,0 @@ -import React from "react"; - -import { shallow } from "enzyme"; - -import { AlertStore } from "Stores/AlertStore"; -import { - StaticColorLabelClassMap, - DefaultLabelClassMap, - AlertNameLabelClassMap, - StateLabelClassMap, -} from "Common/Colors"; -import { BaseLabel } from "."; - -let alertStore; - -beforeEach(() => { - alertStore = new AlertStore([]); -}); - -const FakeBaseLabel = (name = "foo", value = "bar") => { - class RenderableBaseLabel extends BaseLabel { - render() { - const { name, value } = this.props; - let cs = this.getClassAndStyle(name, value); - return ( - - {name}:{" "} - {value} - - ); - } - } - return shallow( - - ); -}; - -describe("", () => { - it("static label uses StaticColorLabelClassMap.badge", () => { - alertStore.settings.values.staticColorLabels = ["foo", "job", "bar"]; - const tree = FakeBaseLabel(); - expect( - tree.find(".components-label").hasClass(StaticColorLabelClassMap.badge) - ).toBe(true); - }); - - Object.entries(StaticColorLabelClassMap).map(([key, val]) => - it(`non-static label doesn't use StaticColorLabelClassMap.${key}`, () => { - alertStore.settings.values.staticColorLabels = []; - const tree = FakeBaseLabel(); - expect(tree.find(".components-label").hasClass(val)).toBe(false); - }) - ); - - it("label with no special color information should use DefaultLabelClassMap.badge", () => { - const tree = FakeBaseLabel(); - expect( - tree.find(".components-label").hasClass(DefaultLabelClassMap.badge) - ).toBe(true); - }); - - it("alertname label should use AlertNameLabelClassMap.badge", () => { - const tree = FakeBaseLabel("alertname", "foo"); - expect( - tree.find(".components-label").hasClass(AlertNameLabelClassMap.badge) - ).toBe(true); - }); - - it("@state=active label should use StateLabelClassMap.active class", () => { - const tree = FakeBaseLabel("@state", "active"); - expect( - tree - .find(".components-label") - .hasClass(`badge-${StateLabelClassMap.active}`) - ).toBe(true); - }); - - it("@state=suppressed label should use StateLabelClassMap.suppressed class", () => { - const tree = FakeBaseLabel("@state", "suppressed"); - expect( - tree - .find(".components-label") - .hasClass(`badge-${StateLabelClassMap.suppressed}`) - ).toBe(true); - }); - - it("@state=unprocessed label should use StateLabelClassMap.unprocessed class", () => { - const tree = FakeBaseLabel("@state", "unprocessed"); - expect( - tree - .find(".components-label") - .hasClass(`badge-${StateLabelClassMap.unprocessed}`) - ).toBe(true); - }); - - it("@state with unknown label should use DefaultLabelClassMap.badge", () => { - const tree = FakeBaseLabel("@state", "foobar"); - expect( - tree.find(".components-label").hasClass(DefaultLabelClassMap.badge) - ).toBe(true); - }); - - it("style prop on a label included in staticColorLabels should be empty", () => { - alertStore.settings.values.staticColorLabels = ["foo", "job", "bar"]; - const tree = FakeBaseLabel(); - expect(tree.find(".components-label").props().style).toEqual({}); - }); - - it("style prop on a label without any color information should be empty", () => { - alertStore.settings.values.staticColorLabels = []; - const tree = FakeBaseLabel(); - expect(tree.find(".components-label").props().style).toEqual({}); - }); -}); diff --git a/ui/src/Components/Labels/FilterInputLabel/index.js b/ui/src/Components/Labels/FilterInputLabel/index.js index 489232e85..03ad0d3d1 100644 --- a/ui/src/Components/Labels/FilterInputLabel/index.js +++ b/ui/src/Components/Labels/FilterInputLabel/index.js @@ -1,7 +1,7 @@ import React from "react"; import PropTypes from "prop-types"; -import { observer } from "mobx-react"; +import { useObserver } from "mobx-react"; import { RIEInput } from "@attently/riek"; @@ -13,103 +13,89 @@ import { faTimes } from "@fortawesome/free-solid-svg-icons/faTimes"; import { AlertStore } from "Stores/AlertStore"; import { QueryOperators } from "Common/Query"; import { TooltipWrapper } from "Components/TooltipWrapper"; -import { BaseLabel } from "Components/Labels/BaseLabel"; +import { GetClassAndStyle } from "Components/Labels/Utils"; -const FilterInputLabel = observer( - class FilterInputLabel extends BaseLabel { - static propTypes = { - alertStore: PropTypes.instanceOf(AlertStore).isRequired, - filter: PropTypes.shape({ - raw: PropTypes.string, - applied: PropTypes.bool, - isValid: PropTypes.bool, - hits: PropTypes.number, - name: PropTypes.string, - matcher: PropTypes.string, - value: PropTypes.string, - }), - }; - - onChange = (update) => { - const { alertStore, filter } = this.props; - - // if filter is empty string then remove it - if (update.raw === "") { - alertStore.filters.removeFilter(filter.raw); - } - - // if not empty replace it - alertStore.filters.replaceFilter(filter.raw, update.raw); - }; - - render() { - const { filter, alertStore } = this.props; - - let cs = this.getClassAndStyle( - filter.matcher === QueryOperators.Equal ? filter.name : "", - filter.matcher === QueryOperators.Equal ? filter.value : "", - "components-filteredinputlabel btn-sm", - "btn" - ); - - const showCounter = - alertStore.filters.values.filter( - (f) => f.hits !== alertStore.info.totalAlerts - ).length > 0; - - const rootClasses = filter.applied - ? cs.className - : [ - "btn-secondary btn-sm components-filteredinputlabel", - ...cs.baseClassNames, - ].join(" "); - - return ( - - {filter.isValid ? ( - filter.applied ? ( - showCounter ? ( - - {filter.hits} - - ) : null - ) : ( - - ) - ) : ( - - )} - - - - alertStore.filters.removeFilter(filter.raw)} - /> - - ); +const FilterInputLabel = ({ alertStore, filter }) => { + const onChange = ({ raw }) => { + // if filter is empty string then remove it + if (raw === "") { + alertStore.filters.removeFilter(filter.raw); } - } -); + // if not empty replace it + alertStore.filters.replaceFilter(filter.raw, raw); + }; + + const cs = GetClassAndStyle( + alertStore, + filter.matcher === QueryOperators.Equal ? filter.name : "", + filter.matcher === QueryOperators.Equal ? filter.value : "", + "components-filteredinputlabel btn-sm", + "btn" + ); + + const showCounter = + alertStore.filters.values.filter( + (f) => f.hits !== alertStore.info.totalAlerts + ).length > 0; + + const rootClasses = filter.applied + ? cs.className + : [ + "btn-secondary btn-sm components-filteredinputlabel", + ...cs.baseClassNames, + ].join(" "); + + return useObserver(() => ( + + {filter.isValid ? ( + filter.applied ? ( + showCounter ? ( + {filter.hits} + ) : null + ) : ( + + ) + ) : ( + + )} + + + + alertStore.filters.removeFilter(filter.raw)} + /> + + )); +}; +FilterInputLabel.propTypes = { + alertStore: PropTypes.instanceOf(AlertStore).isRequired, + filter: PropTypes.shape({ + raw: PropTypes.string, + applied: PropTypes.bool, + isValid: PropTypes.bool, + hits: PropTypes.number, + name: PropTypes.string, + matcher: PropTypes.string, + value: PropTypes.string, + }), +}; export { FilterInputLabel }; diff --git a/ui/src/Components/Labels/FilterInputLabel/index.test.js b/ui/src/Components/Labels/FilterInputLabel/index.test.js index 5c17b9c71..fd18d6b87 100644 --- a/ui/src/Components/Labels/FilterInputLabel/index.test.js +++ b/ui/src/Components/Labels/FilterInputLabel/index.test.js @@ -48,7 +48,9 @@ const ValidateOnChange = (newRaw) => { filter={alertStore.filters.values[0]} /> ); - tree.instance().onChange({ raw: newRaw }); + + const input = tree.find("RIEInput"); + input.props().change({ raw: newRaw }); return tree; }; diff --git a/ui/src/Components/Labels/FilteringCounterBadge/index.js b/ui/src/Components/Labels/FilteringCounterBadge/index.js index 9b653a860..f118e6577 100644 --- a/ui/src/Components/Labels/FilteringCounterBadge/index.js +++ b/ui/src/Components/Labels/FilteringCounterBadge/index.js @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useCallback } from "react"; import PropTypes from "prop-types"; import { observer } from "mobx-react"; @@ -6,70 +6,84 @@ import { observer } from "mobx-react"; import Flash from "react-reveal/Flash"; import { AlertStore } from "Stores/AlertStore"; +import { QueryOperators, FormatQuery } from "Common/Query"; import { TooltipWrapper } from "Components/TooltipWrapper"; -import { BaseLabel } from "Components/Labels/BaseLabel"; +import { GetClassAndStyle } from "Components/Labels/Utils"; // Same as FilteringLabel but for labels that are counters (usually @state) // and only renders a pill badge with the counter, it doesn't render anything // if the counter is 0 const FilteringCounterBadge = observer( - class FilteringCounterBadge extends BaseLabel { - static propTypes = { - alertStore: PropTypes.instanceOf(AlertStore).isRequired, - name: PropTypes.string.isRequired, - value: PropTypes.string.isRequired, - counter: PropTypes.number.isRequired, - themed: PropTypes.bool.isRequired, - alwaysVisible: PropTypes.bool, - defaultColor: PropTypes.oneOf(["light", "primary"]), - }; - static defaultProps = { - defaultColor: "light", - }; + ({ + alertStore, + name, + value, + counter, + themed, + alwaysVisible, + defaultColor, + }) => { + const handleClick = useCallback( + (event) => { + // left click => apply foo=bar filter + // left click + alt => apply foo!=bar filter + const operator = + event.altKey === true + ? QueryOperators.NotEqual + : QueryOperators.Equal; - render() { - const { - name, - value, - counter, - themed, - alwaysVisible, - defaultColor, - } = this.props; + event.preventDefault(); - if (!alwaysVisible && counter === 0) return null; + alertStore.filters.addFilter(FormatQuery(name, operator, value)); + }, + [alertStore.filters, name, value] + ); - const cs = this.getClassAndStyle( - name, - value, - "badge-pill components-label-with-hover" - ); + if (!alwaysVisible && counter === 0) return null; - return ( - - - this.handleClick(e)} - > - {counter} - - - - ); - } + const cs = GetClassAndStyle( + alertStore, + name, + value, + "badge-pill components-label-with-hover" + ); + + return ( + + + + {counter} + + + + ); } ); +FilteringCounterBadge.propTypes = { + alertStore: PropTypes.instanceOf(AlertStore).isRequired, + name: PropTypes.string.isRequired, + value: PropTypes.string.isRequired, + counter: PropTypes.number.isRequired, + themed: PropTypes.bool.isRequired, + alwaysVisible: PropTypes.bool, + defaultColor: PropTypes.oneOf(["light", "primary"]), +}; +FilteringCounterBadge.defaultProps = { + defaultColor: "light", +}; export { FilteringCounterBadge }; diff --git a/ui/src/Components/Labels/FilteringCounterBadge/index.test.js b/ui/src/Components/Labels/FilteringCounterBadge/index.test.js index 11fd4b22c..6194f5641 100644 --- a/ui/src/Components/Labels/FilteringCounterBadge/index.test.js +++ b/ui/src/Components/Labels/FilteringCounterBadge/index.test.js @@ -3,7 +3,7 @@ import React from "react"; import { mount, render } from "enzyme"; import { AlertStore, NewUnappliedFilter } from "Stores/AlertStore"; - +import { QueryOperators } from "Common/Query"; import { FilteringCounterBadge } from "."; let alertStore; @@ -39,7 +39,7 @@ const validateStyle = (value, themed) => { expect(tree.find("span").prop("style")).toEqual({ opacity: 1 }); }; -const validateOnClick = (value, themed) => { +const validateOnClick = (value, themed, isNegative) => { const tree = mount( { themed={themed} /> ); - tree.find(".components-label").simulate("click"); + tree + .find(".components-label") + .simulate("click", { altKey: isNegative ? true : false }); expect(alertStore.filters.values).toHaveLength(1); expect(alertStore.filters.values).toContainEqual( - NewUnappliedFilter(`@state=${value}`) + NewUnappliedFilter( + `@state${ + isNegative ? QueryOperators.NotEqual : QueryOperators.Equal + }${value}` + ) ); }; @@ -93,13 +99,12 @@ describe("", () => { expect(tree.text()).toBe("123"); }); - it("onClick method on @state=unprocessed counter badge should add a new filter", () => { - validateOnClick("unprocessed", true); - }); - it("onClick method on @state=active counter badge should add a new filter", () => { - validateOnClick("active", true); - }); - it("onClick method on @state=suppressed counter badge should add a new filter", () => { - validateOnClick("suppressed", true); - }); + for (let state of ["unprocessed", "active", "suppressed"]) { + it(`click on @state=${state} counter badge should add a new filter`, () => { + validateOnClick(state, true, false); + }); + it(`alt+click method on @state=${state} counter badge should add a new negative filter`, () => { + validateOnClick(state, true, true); + }); + } }); diff --git a/ui/src/Components/Labels/FilteringLabel/index.js b/ui/src/Components/Labels/FilteringLabel/index.js index 2ce1dfe19..c2553d680 100644 --- a/ui/src/Components/Labels/FilteringLabel/index.js +++ b/ui/src/Components/Labels/FilteringLabel/index.js @@ -1,36 +1,41 @@ -import React from "react"; +import React, { useCallback } from "react"; -import { observer } from "mobx-react"; +import { useObserver } from "mobx-react"; +import { QueryOperators, FormatQuery } from "Common/Query"; import { TooltipWrapper } from "Components/TooltipWrapper"; -import { BaseLabel } from "Components/Labels/BaseLabel"; +import { GetClassAndStyle } from "Components/Labels/Utils"; -// Renders a label element that after clicking adds current label as a filter -const FilteringLabel = observer( - class FilteringLabel extends BaseLabel { - render() { - const { name, value } = this.props; +const FilteringLabel = ({ alertStore, name, value }) => { + const handleClick = useCallback( + (event) => { + // left click => apply foo=bar filter + // left click + alt => apply foo!=bar filter + const operator = + event.altKey === true ? QueryOperators.NotEqual : QueryOperators.Equal; - let cs = this.getClassAndStyle( - name, - value, - "components-label-with-hover" - ); + event.preventDefault(); - return ( - - this.handleClick(e)} - > - {name}:{" "} - {value} - - - ); - } - } -); + alertStore.filters.addFilter(FormatQuery(name, operator, value)); + }, + [alertStore.filters, name, value] + ); + + const cs = GetClassAndStyle( + alertStore, + name, + value, + "components-label-with-hover" + ); + + return useObserver(() => ( + + + {name}:{" "} + {value} + + + )); +}; export { FilteringLabel }; diff --git a/ui/src/Components/Labels/HistoryLabel/index.js b/ui/src/Components/Labels/HistoryLabel/index.js index 7b41fcc3f..404e43d5a 100644 --- a/ui/src/Components/Labels/HistoryLabel/index.js +++ b/ui/src/Components/Labels/HistoryLabel/index.js @@ -1,38 +1,32 @@ import React from "react"; import PropTypes from "prop-types"; -import { observer } from "mobx-react"; +import { useObserver } from "mobx-react"; import { QueryOperators } from "Common/Query"; import { AlertStore } from "Stores/AlertStore"; -import { BaseLabel } from "Components/Labels/BaseLabel"; +import { GetClassAndStyle } from "Components/Labels/Utils"; -const HistoryLabel = observer( - class HistoryLabel extends BaseLabel { - static propTypes = { - alertStore: PropTypes.instanceOf(AlertStore).isRequired, - name: PropTypes.string.isRequired, - value: PropTypes.string.isRequired, - matcher: PropTypes.string.isRequired, - }; +const HistoryLabel = ({ alertStore, name, matcher, value }) => { + const cs = GetClassAndStyle( + alertStore, + matcher === QueryOperators.Equal ? name : "", + matcher === QueryOperators.Equal ? value : "", + "components-label-history components-label-value" + ); - render() { - const { name, matcher, value } = this.props; - - let cs = this.getClassAndStyle( - matcher === QueryOperators.Equal ? name : "", - matcher === QueryOperators.Equal ? value : "", - "components-label-history components-label-value" - ); - - return ( - - {name ? `${name}${matcher}` : null} - {value} - - ); - } - } -); + return useObserver(() => ( + + {name ? `${name}${matcher}` : null} + {value} + + )); +}; +HistoryLabel.propTypes = { + alertStore: PropTypes.instanceOf(AlertStore).isRequired, + name: PropTypes.string.isRequired, + value: PropTypes.string.isRequired, + matcher: PropTypes.string.isRequired, +}; export { HistoryLabel }; diff --git a/ui/src/Components/Labels/LabelWithPercent/index.js b/ui/src/Components/Labels/LabelWithPercent/index.js index ef261810f..82a6c155b 100644 --- a/ui/src/Components/Labels/LabelWithPercent/index.js +++ b/ui/src/Components/Labels/LabelWithPercent/index.js @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useCallback } from "react"; import PropTypes from "prop-types"; import { observer } from "mobx-react"; @@ -8,82 +8,93 @@ import { faTimes } from "@fortawesome/free-solid-svg-icons/faTimes"; import { AlertStore } from "Stores/AlertStore"; import { QueryOperators, FormatQuery } from "Common/Query"; -import { BaseLabel } from "Components/Labels/BaseLabel"; +import { GetClassAndStyle } from "Components/Labels/Utils"; const LabelWithPercent = observer( - class LabelWithPercent extends BaseLabel { - static propTypes = { - alertStore: PropTypes.instanceOf(AlertStore).isRequired, - name: PropTypes.string.isRequired, - value: PropTypes.string.isRequired, - hits: PropTypes.number.isRequired, - percent: PropTypes.number.isRequired, - offset: PropTypes.number.isRequired, - isActive: PropTypes.bool.isRequired, - }; + ({ alertStore, name, value, hits, percent, offset, isActive }) => { + const handleClick = useCallback( + (event) => { + // left click => apply foo=bar filter + // left click + alt => apply foo!=bar filter + const operator = + event.altKey === true + ? QueryOperators.NotEqual + : QueryOperators.Equal; - removeFromFilters = () => { - const { alertStore, name, value } = this.props; + event.preventDefault(); + + alertStore.filters.addFilter(FormatQuery(name, operator, value)); + }, + [alertStore.filters, name, value] + ); + + const removeFromFilters = () => { alertStore.filters.removeFilter( FormatQuery(name, QueryOperators.Equal, value) ); }; - render() { - const { name, value, hits, percent, offset, isActive } = this.props; + const cs = GetClassAndStyle( + alertStore, + name, + value, + "components-label-with-hover mb-0 pl-0 text-left" + ); - let cs = this.getClassAndStyle( - name, - value, - "components-label-with-hover mb-0 pl-0 text-left" - ); + const progressBarBg = + percent > 66 ? "bg-danger" : percent > 33 ? "bg-warning" : "bg-success"; - const progressBarBg = - percent > 66 ? "bg-danger" : percent > 33 ? "bg-warning" : "bg-success"; - - return ( - - - - {hits} - - this.handleClick(e)}> - {name}:{" "} - {value} - - {isActive ? ( - - ) : null} + return ( + + + + {hits} - - {offset === 0 ? null : ( - - )} + + {name}:{" "} + {value} + + {isActive ? ( + + ) : null} + + + {offset === 0 ? null : ( - + )} + - ); - } + + ); } ); +LabelWithPercent.propTypes = { + alertStore: PropTypes.instanceOf(AlertStore).isRequired, + name: PropTypes.string.isRequired, + value: PropTypes.string.isRequired, + hits: PropTypes.number.isRequired, + percent: PropTypes.number.isRequired, + offset: PropTypes.number.isRequired, + isActive: PropTypes.bool.isRequired, +}; export { LabelWithPercent }; diff --git a/ui/src/Components/Labels/StaticLabel/index.js b/ui/src/Components/Labels/StaticLabel/index.js index 0439991f6..6a9f8f475 100644 --- a/ui/src/Components/Labels/StaticLabel/index.js +++ b/ui/src/Components/Labels/StaticLabel/index.js @@ -1,25 +1,19 @@ import React from "react"; -import { observer } from "mobx-react"; +import { useObserver } from "mobx-react"; -import { BaseLabel } from "Components/Labels/BaseLabel"; +import { GetClassAndStyle } from "Components/Labels/Utils"; // Renders a static label element, no click actions, no hover -const StaticLabel = observer( - class StaticLabel extends BaseLabel { - render() { - const { name, value } = this.props; +const StaticLabel = ({ alertStore, name, value }) => { + const cs = GetClassAndStyle(alertStore, name, value); - let cs = this.getClassAndStyle(name, value); - - return ( - - {name}:{" "} - {value} - - ); - } - } -); + return useObserver(() => ( + + {name}:{" "} + {value} + + )); +}; export { StaticLabel }; diff --git a/ui/src/Components/Labels/Utils.js b/ui/src/Components/Labels/Utils.js new file mode 100644 index 000000000..a113c8a39 --- /dev/null +++ b/ui/src/Components/Labels/Utils.js @@ -0,0 +1,63 @@ +import { + StaticColorLabelClassMap, + DefaultLabelClassMap, + AlertNameLabelClassMap, + StateLabelClassMap, +} from "Common/Colors"; +import { StaticLabels } from "Common/Query"; + +const isBackgroundDark = (brightness) => brightness <= 125; + +const GetClassAndStyle = (alertStore, name, value, extraClass, baseClass) => { + const elementType = baseClass || "badge"; + + const data = { + style: {}, + className: "", + baseClassNames: ["components-label", elementType], + colorClassNames: [], + }; + + if (name === StaticLabels.AlertName) { + data.colorClassNames.push(AlertNameLabelClassMap[elementType]); + } else if (name === StaticLabels.State) { + data.colorClassNames.push( + StateLabelClassMap[value] + ? `${elementType}-${StateLabelClassMap[value]}` + : DefaultLabelClassMap[elementType] + ); + } else if (alertStore.settings.values.staticColorLabels.includes(name)) { + data.colorClassNames.push(StaticColorLabelClassMap[elementType]); + } else { + const c = alertStore.data.getColorData(name, value); + if (c) { + // if there's color information use it + data.style["backgroundColor"] = `rgba(${[ + c.background.red, + c.background.green, + c.background.blue, + c.background.alpha, + ].join(", ")})`; + + data.colorClassNames.push( + isBackgroundDark(c.brightness) + ? "components-label-dark" + : "components-label-bright" + ); + + data.colorClassNames.push( + `components-label-brightness-${Math.round(c.brightness / 25)}` + ); + } else { + // if not fall back to class + data.colorClassNames.push(DefaultLabelClassMap[elementType]); + } + } + data.className = `${[...data.baseClassNames, ...data.colorClassNames].join( + " " + )} ${extraClass || ""}`; + + return data; +}; + +export { GetClassAndStyle }; diff --git a/ui/src/Components/Labels/Utils.test.js b/ui/src/Components/Labels/Utils.test.js new file mode 100644 index 000000000..c93a5d351 --- /dev/null +++ b/ui/src/Components/Labels/Utils.test.js @@ -0,0 +1,76 @@ +import { AlertStore } from "Stores/AlertStore"; +import { + StaticColorLabelClassMap, + DefaultLabelClassMap, + AlertNameLabelClassMap, + StateLabelClassMap, +} from "Common/Colors"; +import { GetClassAndStyle } from "./Utils"; + +let alertStore; + +beforeEach(() => { + alertStore = new AlertStore([]); +}); + +describe("", () => { + it("static label uses StaticColorLabelClassMap.badge", () => { + alertStore.settings.values.staticColorLabels = ["foo", "job", "bar"]; + const cs = GetClassAndStyle(alertStore, "foo", "bar"); + expect(cs.colorClassNames).toContain(StaticColorLabelClassMap.badge); + }); + + Object.entries(StaticColorLabelClassMap).map(([key, val]) => + it(`non-static label doesn't use StaticColorLabelClassMap.${key}`, () => { + alertStore.settings.values.staticColorLabels = []; + const cs = GetClassAndStyle(alertStore, "foo", "bar"); + expect(cs.colorClassNames).not.toContain(StaticColorLabelClassMap.badge); + }) + ); + + it("label with no special color information should use DefaultLabelClassMap.badge", () => { + const cs = GetClassAndStyle(alertStore, "foo", "bar"); + expect(cs.colorClassNames).toContain(DefaultLabelClassMap.badge); + }); + + it("alertname label should use AlertNameLabelClassMap.badge", () => { + const cs = GetClassAndStyle(alertStore, "alertname", "foo"); + expect(cs.colorClassNames).toContain(AlertNameLabelClassMap.badge); + }); + + it("@state=active label should use StateLabelClassMap.active class", () => { + const cs = GetClassAndStyle(alertStore, "@state", "active"); + expect(cs.colorClassNames).toContain(`badge-${StateLabelClassMap.active}`); + }); + + it("@state=suppressed label should use StateLabelClassMap.suppressed class", () => { + const cs = GetClassAndStyle(alertStore, "@state", "suppressed"); + expect(cs.colorClassNames).toContain( + `badge-${StateLabelClassMap.suppressed}` + ); + }); + + it("@state=unprocessed label should use StateLabelClassMap.unprocessed class", () => { + const cs = GetClassAndStyle(alertStore, "@state", "unprocessed"); + expect(cs.colorClassNames).toContain( + `badge-${StateLabelClassMap.unprocessed}` + ); + }); + + it("@state with unknown label should use DefaultLabelClassMap.badge", () => { + const cs = GetClassAndStyle(alertStore, "@state", "foobar"); + expect(cs.colorClassNames).toContain(DefaultLabelClassMap.badge); + }); + + it("style prop on a label included in staticColorLabels should be empty", () => { + alertStore.settings.values.staticColorLabels = ["foo", "job", "bar"]; + const cs = GetClassAndStyle(alertStore, "foo", "bar"); + expect(cs.style).toEqual({}); + }); + + it("style prop on a label without any color information should be empty", () => { + alertStore.settings.values.staticColorLabels = []; + const cs = GetClassAndStyle(alertStore, "foo", "bar"); + expect(cs.style).toEqual({}); + }); +});