option.value}\n getOptionLabel={option => this.getAccountBadge(option.value)}\n filterOption={this.filterOption}\n getNewOptionData={this.getNewOptionData}\n isClearable\n styles={this.props.styles}\n isDisabled={this.props.disabled}\n />\n )\n }\n}\n\nAccountSelector.defaultProps = {\n isMulti: true,\n}\n\nAccountSelector.propTypes = {\n accounts: ImmutablePropTypes.iterable.isRequired,\n value: PropTypes.any,\n onChange: PropTypes.func.isRequired,\n isMulti: PropTypes.bool,\n styles: PropTypes.object,\n disabled: PropTypes.bool,\n hasAccountNumber: PropTypes.bool,\n}\nexport default AccountSelector\n","import React from 'react'\nimport PropTypes from 'prop-types'\nimport { FILTER_DATE_FORMAT } from 'appConstants'\nimport moment from 'moment'\nimport { DateRangePicker as DateRangePickerLib } from 'react-dates'\n\nclass DateRangePicker extends React.Component {\n constructor(props) {\n super(props)\n\n this.state = {\n startDate: props.startDate ? moment(props.startDate) : null,\n endDate: props.endDate ? moment(props.endDate) : null,\n focusedInput: null,\n }\n }\n\n componentDidUpdate(oldProps) {\n if (oldProps.startDate !== this.props.startDate) {\n this.setState({\n startDate: this.props.startDate ? moment(this.props.startDate) : null,\n })\n }\n\n if (oldProps.endDate !== this.props.endDate) {\n this.setState({\n endDate: this.props.startDate ? moment(this.props.endDate) : null,\n })\n }\n }\n\n onDatesChange = ({ startDate, endDate }) => {\n const formattedStartDate = startDate\n ? startDate.format(FILTER_DATE_FORMAT)\n : null\n\n const formattedEndDate = endDate ? endDate.format(FILTER_DATE_FORMAT) : null\n\n this.props.onChange({\n startDate: formattedStartDate,\n endDate: formattedEndDate,\n })\n\n this.setState({\n startDate,\n endDate,\n })\n }\n\n render() {\n return (\n \n this.setState({ focusedInput })}\n onDatesChange={this.onDatesChange}\n isOutsideRange={date => moment(date).isAfter(moment().endOf('day'))}\n displayFormat={'MMMM D YYYY'}\n />\n
\n )\n }\n}\n\nDateRangePicker.propTypes = {\n onChange: PropTypes.func.isRequired,\n startDate: PropTypes.string,\n endDate: PropTypes.string,\n}\n\nexport default DateRangePicker\n","//http://bernii.github.io/gauge.js/\n//MIT licensed\n// Generated by CoffeeScript 1.10.0\n;(function() {\n var AnimatedText,\n AnimatedTextFactory,\n Bar,\n BaseDonut,\n BaseGauge,\n Donut,\n Gauge,\n GaugePointer,\n TextRenderer,\n ValueUpdater,\n addCommas,\n cutHex,\n formatNumber,\n mergeObjects,\n secondsToString,\n slice = [].slice,\n hasProp = {}.hasOwnProperty,\n extend = function(child, parent) {\n for (var key in parent) {\n if (hasProp.call(parent, key)) child[key] = parent[key]\n }\n function ctor() {\n this.constructor = child\n }\n ctor.prototype = parent.prototype\n child.prototype = new ctor()\n child.__super__ = parent.prototype\n return child\n }\n ;(function() {\n var browserRequestAnimationFrame,\n isCancelled,\n j,\n lastId,\n len,\n vendor,\n vendors\n vendors = ['ms', 'moz', 'webkit', 'o']\n for (j = 0, len = vendors.length; j < len; j++) {\n vendor = vendors[j]\n if (window.requestAnimationFrame) {\n break\n }\n window.requestAnimationFrame = window[vendor + 'RequestAnimationFrame']\n window.cancelAnimationFrame =\n window[vendor + 'CancelAnimationFrame'] ||\n window[vendor + 'CancelRequestAnimationFrame']\n }\n browserRequestAnimationFrame = null\n lastId = 0\n isCancelled = {}\n if (!requestAnimationFrame) {\n window.requestAnimationFrame = function(callback, element) {\n var currTime, id, lastTime, timeToCall\n currTime = new Date().getTime()\n timeToCall = Math.max(0, 16 - (currTime - lastTime))\n id = window.setTimeout(function() {\n return callback(currTime + timeToCall)\n }, timeToCall)\n lastTime = currTime + timeToCall\n return id\n }\n return (window.cancelAnimationFrame = function(id) {\n return clearTimeout(id)\n })\n } else if (!window.cancelAnimationFrame) {\n browserRequestAnimationFrame = window.requestAnimationFrame\n window.requestAnimationFrame = function(callback, element) {\n var myId\n myId = ++lastId\n browserRequestAnimationFrame(function() {\n if (!isCancelled[myId]) {\n return callback()\n }\n }, element)\n return myId\n }\n return (window.cancelAnimationFrame = function(id) {\n return (isCancelled[id] = true)\n })\n }\n })()\n\n secondsToString = function(sec) {\n var hr, min\n hr = Math.floor(sec / 3600)\n min = Math.floor((sec - hr * 3600) / 60)\n sec -= hr * 3600 + min * 60\n sec += ''\n min += ''\n while (min.length < 2) {\n min = '0' + min\n }\n while (sec.length < 2) {\n sec = '0' + sec\n }\n hr = hr ? hr + ':' : ''\n return hr + min + ':' + sec\n }\n\n formatNumber = function() {\n var digits, num, value\n num = 1 <= arguments.length ? slice.call(arguments, 0) : []\n value = num[0]\n digits = 0 || num[1]\n return addCommas(value.toFixed(digits))\n }\n\n mergeObjects = function(obj1, obj2) {\n var key, out, val\n out = {}\n for (key in obj1) {\n if (!hasProp.call(obj1, key)) continue\n val = obj1[key]\n out[key] = val\n }\n for (key in obj2) {\n if (!hasProp.call(obj2, key)) continue\n val = obj2[key]\n out[key] = val\n }\n return out\n }\n\n addCommas = function(nStr) {\n var rgx, x, x1, x2\n nStr += ''\n x = nStr.split('.')\n x1 = x[0]\n x2 = ''\n if (x.length > 1) {\n x2 = '.' + x[1]\n }\n rgx = /(\\d+)(\\d{3})/\n while (rgx.test(x1)) {\n x1 = x1.replace(rgx, '$1' + ',' + '$2')\n }\n return x1 + x2\n }\n\n cutHex = function(nStr) {\n if (nStr.charAt(0) === '#') {\n return nStr.substring(1, 7)\n }\n return nStr\n }\n\n ValueUpdater = (function() {\n ValueUpdater.prototype.animationSpeed = 32\n\n function ValueUpdater(addToAnimationQueue, clear) {\n if (addToAnimationQueue == null) {\n addToAnimationQueue = true\n }\n this.clear = clear != null ? clear : true\n if (addToAnimationQueue) {\n AnimationUpdater.add(this)\n }\n }\n\n ValueUpdater.prototype.update = function(force) {\n var diff\n if (force == null) {\n force = false\n }\n if (force || this.displayedValue !== this.value) {\n if (this.ctx && this.clear) {\n this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)\n }\n diff = this.value - this.displayedValue\n if (Math.abs(diff / this.animationSpeed) <= 0.001) {\n this.displayedValue = this.value\n } else {\n this.displayedValue = this.displayedValue + diff / this.animationSpeed\n }\n this.render()\n return true\n }\n return false\n }\n\n return ValueUpdater\n })()\n\n BaseGauge = (function(superClass) {\n extend(BaseGauge, superClass)\n\n function BaseGauge() {\n return BaseGauge.__super__.constructor.apply(this, arguments)\n }\n\n BaseGauge.prototype.displayScale = 1\n\n BaseGauge.prototype.forceUpdate = true\n\n BaseGauge.prototype.setTextField = function(textField, fractionDigits) {\n return (this.textField =\n textField instanceof TextRenderer\n ? textField\n : new TextRenderer(textField, fractionDigits))\n }\n\n BaseGauge.prototype.setMinValue = function(minValue, updateStartValue) {\n var gauge, j, len, ref, results\n this.minValue = minValue\n if (updateStartValue == null) {\n updateStartValue = true\n }\n if (updateStartValue) {\n this.displayedValue = this.minValue\n ref = this.gp || []\n results = []\n for (j = 0, len = ref.length; j < len; j++) {\n gauge = ref[j]\n results.push((gauge.displayedValue = this.minValue))\n }\n return results\n }\n }\n\n BaseGauge.prototype.setOptions = function(options) {\n if (options == null) {\n options = null\n }\n this.options = mergeObjects(this.options, options)\n if (this.textField) {\n this.textField.el.style.fontSize = options.fontSize + 'px'\n }\n if (this.options.angle > 0.5) {\n this.options.angle = 0.5\n }\n this.configDisplayScale()\n return this\n }\n\n BaseGauge.prototype.configDisplayScale = function() {\n var backingStorePixelRatio,\n devicePixelRatio,\n height,\n prevDisplayScale,\n width\n prevDisplayScale = this.displayScale\n if (this.options.highDpiSupport === false) {\n delete this.displayScale\n } else {\n devicePixelRatio = window.devicePixelRatio || 1\n backingStorePixelRatio =\n this.ctx.webkitBackingStorePixelRatio ||\n this.ctx.mozBackingStorePixelRatio ||\n this.ctx.msBackingStorePixelRatio ||\n this.ctx.oBackingStorePixelRatio ||\n this.ctx.backingStorePixelRatio ||\n 1\n this.displayScale = devicePixelRatio / backingStorePixelRatio\n }\n if (this.displayScale !== prevDisplayScale) {\n width = this.canvas.G__width || this.canvas.width\n height = this.canvas.G__height || this.canvas.height\n this.canvas.width = width * this.displayScale\n this.canvas.height = height * this.displayScale\n this.canvas.style.width = width + 'px'\n this.canvas.style.height = height + 'px'\n this.canvas.G__width = width\n this.canvas.G__height = height\n }\n return this\n }\n\n BaseGauge.prototype.parseValue = function(value) {\n value = parseFloat(value) || Number(value)\n if (isFinite(value)) {\n return value\n } else {\n return 0\n }\n }\n\n return BaseGauge\n })(ValueUpdater)\n\n TextRenderer = (function() {\n function TextRenderer(el, fractionDigits1) {\n this.el = el\n this.fractionDigits = fractionDigits1\n }\n\n TextRenderer.prototype.render = function(gauge) {\n return (this.el.innerHTML = formatNumber(\n gauge.displayedValue,\n this.fractionDigits\n ))\n }\n\n return TextRenderer\n })()\n\n AnimatedText = (function(superClass) {\n extend(AnimatedText, superClass)\n\n AnimatedText.prototype.displayedValue = 0\n\n AnimatedText.prototype.value = 0\n\n AnimatedText.prototype.setVal = function(value) {\n return (this.value = 1 * value)\n }\n\n function AnimatedText(elem1, text) {\n this.elem = elem1\n this.text = text != null ? text : false\n AnimatedText.__super__.constructor.call(this)\n if (this.elem === void 0) {\n throw new Error(\"The element isn't defined.\")\n }\n this.value = 1 * this.elem.innerHTML\n if (this.text) {\n this.value = 0\n }\n }\n\n AnimatedText.prototype.render = function() {\n var textVal\n if (this.text) {\n textVal = secondsToString(this.displayedValue.toFixed(0))\n } else {\n textVal = addCommas(formatNumber(this.displayedValue))\n }\n return (this.elem.innerHTML = textVal)\n }\n\n return AnimatedText\n })(ValueUpdater)\n\n AnimatedTextFactory = {\n create: function(objList) {\n var elem, j, len, out\n out = []\n for (j = 0, len = objList.length; j < len; j++) {\n elem = objList[j]\n out.push(new AnimatedText(elem))\n }\n return out\n },\n }\n\n GaugePointer = (function(superClass) {\n extend(GaugePointer, superClass)\n\n GaugePointer.prototype.displayedValue = 0\n\n GaugePointer.prototype.value = 0\n\n GaugePointer.prototype.hovered = false\n\n GaugePointer.prototype.options = {\n strokeWidth: 0.035,\n length: 0.1,\n color: '#000000',\n iconPath: null,\n iconScale: 1.0,\n iconAngle: 0,\n }\n\n GaugePointer.prototype.img = null\n\n function GaugePointer(gauge1) {\n this.gauge = gauge1\n if (this.gauge === void 0) {\n throw new Error(\"The element isn't defined.\")\n }\n this.ctx = this.gauge.ctx\n this.canvas = this.gauge.canvas\n GaugePointer.__super__.constructor.call(this, false, false)\n this.setOptions()\n\n this.canvas.onmouseenter = () => {\n this.setHovered(true)\n this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)\n this.gauge.render()\n }\n this.canvas.onmouseleave = () => {\n this.setHovered(false)\n this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)\n this.gauge.render()\n }\n }\n\n GaugePointer.prototype.setHovered = function(hoverValue) {\n this.hovered = hoverValue\n }\n\n GaugePointer.prototype.setOptions = function(options) {\n if (options == null) {\n options = null\n }\n this.options = mergeObjects(this.options, options)\n this.length =\n 2 *\n this.gauge.radius *\n this.gauge.options.radiusScale *\n this.options.length\n this.strokeWidth = this.canvas.height * this.options.strokeWidth\n this.maxValue = this.gauge.maxValue\n this.minValue = this.gauge.minValue\n this.animationSpeed = this.gauge.animationSpeed\n this.options.angle = this.gauge.options.angle\n if (this.options.iconPath) {\n this.img = new Image()\n return (this.img.src = this.options.iconPath)\n }\n }\n\n GaugePointer.prototype.render = function() {\n var angle, endX, endY, imgX, imgY, startX, startY, x, y\n angle = this.gauge.getAngle.call(this, this.displayedValue)\n x = Math.round(this.length * Math.cos(angle))\n y = Math.round(this.length * Math.sin(angle))\n startX = Math.round(this.strokeWidth * Math.cos(angle - Math.PI / 2))\n startY = Math.round(this.strokeWidth * Math.sin(angle - Math.PI / 2))\n endX = Math.round(this.strokeWidth * Math.cos(angle + Math.PI / 2))\n endY = Math.round(this.strokeWidth * Math.sin(angle + Math.PI / 2))\n this.ctx.beginPath()\n this.ctx.fillStyle = this.hovered\n ? this.options.hoveredColor\n : this.options.color\n this.ctx.arc(0, 0, this.strokeWidth, 0, Math.PI * 2, false)\n this.ctx.fill()\n this.ctx.beginPath()\n this.ctx.moveTo(startX, startY)\n this.ctx.lineTo(x, y)\n this.ctx.lineTo(endX, endY)\n this.ctx.fill()\n if (this.img) {\n imgX = Math.round(this.img.width * this.options.iconScale)\n imgY = Math.round(this.img.height * this.options.iconScale)\n this.ctx.save()\n this.ctx.translate(x, y)\n this.ctx.rotate(\n angle + (Math.PI / 180.0) * (90 + this.options.iconAngle)\n )\n this.ctx.drawImage(this.img, -imgX / 2, -imgY / 2, imgX, imgY)\n return this.ctx.restore()\n }\n\n if (this.hovered) {\n this.ctx.save()\n this.ctx.translate(x, y)\n this.ctx.font = '15px sans-serif'\n const label = `${formatNumber(this.value, 0)}%`\n const textWidth = this.ctx.measureText('Hello').width\n const textX = -textWidth / 2\n const textY = -30 / 2\n this.ctx.beginPath()\n this.ctx.arc(textX + 15, textY - 5, (textWidth + 4) / 2, 0, 2 * Math.PI)\n this.ctx.save()\n this.ctx.shadowColor = '#888'\n this.ctx.shadowBlur = 8\n this.ctx.shadowOffsetY = 4\n this.ctx.fillStyle = '#fff'\n this.ctx.fill()\n this.ctx.restore()\n this.ctx.lineWidth = 2\n this.ctx.strokeStyle = this.options.valueCircleColor || '#ccc'\n this.ctx.stroke()\n\n this.ctx.fillStyle = this.options.valueColor || '#555'\n this.ctx.fillText(label, textX, textY)\n this.ctx.fillStyle = 'black'\n return this.ctx.restore()\n }\n }\n\n return GaugePointer\n })(ValueUpdater)\n\n Bar = (function() {\n function Bar(elem1) {\n this.elem = elem1\n }\n\n Bar.prototype.updateValues = function(arrValues) {\n this.value = arrValues[0]\n this.maxValue = arrValues[1]\n this.avgValue = arrValues[2]\n return this.render()\n }\n\n Bar.prototype.render = function() {\n var avgPercent, valPercent\n if (this.textField) {\n this.textField.text(formatNumber(this.value))\n }\n if (this.maxValue === 0) {\n this.maxValue = this.avgValue * 2\n }\n valPercent = (this.value / this.maxValue) * 100\n avgPercent = (this.avgValue / this.maxValue) * 100\n $('.bar-value', this.elem).css({\n width: valPercent + '%',\n })\n return $('.typical-value', this.elem).css({\n width: avgPercent + '%',\n })\n }\n\n return Bar\n })()\n\n Gauge = (function(superClass) {\n extend(Gauge, superClass)\n\n Gauge.prototype.elem = null\n\n Gauge.prototype.value = [20]\n\n Gauge.prototype.maxValue = 80\n\n Gauge.prototype.minValue = 0\n\n Gauge.prototype.displayedAngle = 0\n\n Gauge.prototype.displayedValue = 0\n\n Gauge.prototype.lineWidth = 40\n\n Gauge.prototype.paddingTop = 0.1\n\n Gauge.prototype.paddingBottom = 0.1\n\n Gauge.prototype.percentColors = null\n\n Gauge.prototype.options = {\n colorStart: '#6fadcf',\n colorStop: void 0,\n gradientType: 0,\n strokeColor: '#e0e0e0',\n pointer: {\n length: 0.8,\n strokeWidth: 0.035,\n iconScale: 1.0,\n },\n angle: 0.15,\n lineWidth: 0.44,\n radiusScale: 1.0,\n fontSize: 40,\n limitMax: false,\n limitMin: false,\n }\n\n function Gauge(canvas) {\n var h, w\n this.canvas = canvas\n Gauge.__super__.constructor.call(this)\n this.percentColors = null\n if (typeof G_vmlCanvasManager !== 'undefined') {\n this.canvas = window.G_vmlCanvasManager.initElement(this.canvas)\n }\n this.ctx = this.canvas.getContext('2d')\n h = this.canvas.clientHeight\n w = this.canvas.clientWidth\n this.canvas.height = h\n this.canvas.width = w\n this.gp = [new GaugePointer(this)]\n this.setOptions()\n }\n\n Gauge.prototype.setOptions = function(options) {\n var gauge, j, len, phi, ref\n if (options == null) {\n options = null\n }\n Gauge.__super__.setOptions.call(this, options)\n this.configPercentColors()\n this.extraPadding = 0\n if (this.options.angle < 0) {\n phi = Math.PI * (1 + this.options.angle)\n this.extraPadding = Math.sin(phi)\n }\n this.availableHeight =\n this.canvas.height * (1 - this.paddingTop - this.paddingBottom)\n this.lineWidth = this.availableHeight * this.options.lineWidth\n this.radius =\n (this.availableHeight - this.lineWidth / 2) / (1.0 + this.extraPadding)\n this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)\n ref = this.gp\n for (j = 0, len = ref.length; j < len; j++) {\n gauge = ref[j]\n gauge.setOptions(this.options.pointer)\n //gauge.render()\n }\n this.render()\n return this\n }\n\n Gauge.prototype.configPercentColors = function() {\n var bval, gval, i, j, ref, results, rval\n this.percentColors = null\n if (this.options.percentColors !== void 0) {\n this.percentColors = new Array()\n results = []\n for (\n i = j = 0, ref = this.options.percentColors.length - 1;\n 0 <= ref ? j <= ref : j >= ref;\n i = 0 <= ref ? ++j : --j\n ) {\n rval = parseInt(\n cutHex(this.options.percentColors[i][1]).substring(0, 2),\n 16\n )\n gval = parseInt(\n cutHex(this.options.percentColors[i][1]).substring(2, 4),\n 16\n )\n bval = parseInt(\n cutHex(this.options.percentColors[i][1]).substring(4, 6),\n 16\n )\n results.push(\n (this.percentColors[i] = {\n pct: this.options.percentColors[i][0],\n color: {\n r: rval,\n g: gval,\n b: bval,\n },\n })\n )\n }\n return results\n }\n }\n\n Gauge.prototype.set = function(value) {\n var gp, i, j, k, l, len, ref, ref1, val\n if (!(value instanceof Array)) {\n value = [value]\n }\n for (\n i = j = 0, ref = value.length - 1;\n 0 <= ref ? j <= ref : j >= ref;\n i = 0 <= ref ? ++j : --j\n ) {\n value[i] = this.parseValue(value[i])\n }\n if (value.length > this.gp.length) {\n for (\n i = k = 0, ref1 = value.length - this.gp.length;\n 0 <= ref1 ? k < ref1 : k > ref1;\n i = 0 <= ref1 ? ++k : --k\n ) {\n gp = new GaugePointer(this)\n gp.setOptions(this.options.pointer)\n this.gp.push(gp)\n }\n } else if (value.length < this.gp.length) {\n this.gp = this.gp.slice(this.gp.length - value.length)\n }\n i = 0\n for (l = 0, len = value.length; l < len; l++) {\n val = value[l]\n if (val > this.maxValue) {\n if (this.options.limitMax) {\n val = this.maxValue\n } else {\n this.maxValue = val + 1\n }\n } else if (val < this.minValue) {\n if (this.options.limitMin) {\n val = this.minValue\n } else {\n this.minValue = val - 1\n }\n }\n this.gp[i].value = val\n this.gp[i++].setOptions({\n minValue: this.minValue,\n maxValue: this.maxValue,\n angle: this.options.angle,\n })\n }\n this.value = Math.max(\n Math.min(value[value.length - 1], this.maxValue),\n this.minValue\n )\n AnimationUpdater.run(this.forceUpdate)\n return (this.forceUpdate = false)\n }\n\n Gauge.prototype.getAngle = function(value) {\n return (\n (1 + this.options.angle) * Math.PI +\n ((value - this.minValue) / (this.maxValue - this.minValue)) *\n (1 - this.options.angle * 2) *\n Math.PI\n )\n }\n\n Gauge.prototype.getColorForPercentage = function(pct, grad) {\n var color, endColor, i, j, rangePct, ref, startColor\n if (pct === 0) {\n color = this.percentColors[0].color\n } else {\n color = this.percentColors[this.percentColors.length - 1].color\n for (\n i = j = 0, ref = this.percentColors.length - 1;\n 0 <= ref ? j <= ref : j >= ref;\n i = 0 <= ref ? ++j : --j\n ) {\n if (pct <= this.percentColors[i].pct) {\n if (grad === true) {\n startColor = this.percentColors[i - 1] || this.percentColors[0]\n endColor = this.percentColors[i]\n rangePct =\n (pct - startColor.pct) / (endColor.pct - startColor.pct)\n color = {\n r: Math.floor(\n startColor.color.r * (1 - rangePct) +\n endColor.color.r * rangePct\n ),\n g: Math.floor(\n startColor.color.g * (1 - rangePct) +\n endColor.color.g * rangePct\n ),\n b: Math.floor(\n startColor.color.b * (1 - rangePct) +\n endColor.color.b * rangePct\n ),\n }\n } else {\n color = this.percentColors[i].color\n }\n break\n }\n }\n }\n return 'rgb(' + [color.r, color.g, color.b].join(',') + ')'\n }\n\n Gauge.prototype.getColorForValue = function(val, grad) {\n var pct\n pct = (val - this.minValue) / (this.maxValue - this.minValue)\n return this.getColorForPercentage(pct, grad)\n }\n\n Gauge.prototype.renderStaticLabels = function(staticLabels, w, h, radius) {\n var font, fontsize, j, len, match, re, ref, rest, rotationAngle, value\n this.ctx.save()\n this.ctx.translate(w, h)\n font = staticLabels.font || '10px Times'\n re = /\\d+\\.?\\d?/\n match = font.match(re)[0]\n rest = font.slice(match.length)\n fontsize = parseFloat(match) * this.displayScale\n this.ctx.font = fontsize + rest\n this.ctx.fillStyle = staticLabels.color || '#000000'\n this.ctx.textBaseline = 'bottom'\n this.ctx.textAlign = 'center'\n ref = staticLabels.labels\n for (j = 0, len = ref.length; j < len; j++) {\n value = ref[j]\n if (value.label !== void 0) {\n if (\n (!this.options.limitMin || value >= this.minValue) &&\n (!this.options.limitMax || value <= this.maxValue)\n ) {\n font = value.font || staticLabels.font\n match = font.match(re)[0]\n rest = font.slice(match.length)\n fontsize = parseFloat(match) * this.displayScale\n this.ctx.font = fontsize + rest\n rotationAngle = this.getAngle(value.label) - (3 * Math.PI) / 2\n this.ctx.rotate(rotationAngle)\n this.ctx.fillText(\n formatNumber(value.label, staticLabels.fractionDigits),\n 0,\n -radius - this.lineWidth / 2\n )\n this.ctx.rotate(-rotationAngle)\n }\n } else {\n if (\n (!this.options.limitMin || value >= this.minValue) &&\n (!this.options.limitMax || value <= this.maxValue)\n ) {\n rotationAngle = this.getAngle(value) - (3 * Math.PI) / 2\n this.ctx.rotate(rotationAngle)\n this.ctx.fillText(\n formatNumber(value, staticLabels.fractionDigits),\n 0,\n -radius - this.lineWidth / 2\n )\n this.ctx.rotate(-rotationAngle)\n }\n }\n }\n return this.ctx.restore()\n }\n\n Gauge.prototype.renderTicks = function(ticksOptions, w, h, radius) {\n var currentDivision,\n currentSubDivision,\n divColor,\n divLength,\n divWidth,\n divisionCount,\n j,\n lineWidth,\n range,\n rangeDivisions,\n ref,\n results,\n scaleMutate,\n st,\n subColor,\n subDivisions,\n subLength,\n subWidth,\n subdivisionCount,\n t,\n tmpRadius\n if (ticksOptions !== {}) {\n divisionCount = ticksOptions.divisions || 0\n subdivisionCount = ticksOptions.subDivisions || 0\n divColor = ticksOptions.divColor || '#fff'\n subColor = ticksOptions.subColor || '#fff'\n divLength = ticksOptions.divLength || 0.7\n subLength = ticksOptions.subLength || 0.2\n range = parseFloat(this.maxValue) - parseFloat(this.minValue)\n rangeDivisions = parseFloat(range) / parseFloat(ticksOptions.divisions)\n subDivisions =\n parseFloat(rangeDivisions) / parseFloat(ticksOptions.subDivisions)\n currentDivision = parseFloat(this.minValue)\n currentSubDivision = 0.0 + subDivisions\n lineWidth = range / 400\n divWidth = lineWidth * (ticksOptions.divWidth || 1)\n subWidth = lineWidth * (ticksOptions.subWidth || 1)\n results = []\n for (t = j = 0, ref = divisionCount + 1; j < ref; t = j += 1) {\n this.ctx.lineWidth = this.lineWidth * divLength\n scaleMutate = (this.lineWidth / 2) * (1 - divLength)\n tmpRadius = this.radius * this.options.radiusScale + scaleMutate\n this.ctx.strokeStyle = divColor\n this.ctx.beginPath()\n this.ctx.arc(\n 0,\n 0,\n tmpRadius,\n this.getAngle(currentDivision - divWidth),\n this.getAngle(currentDivision + divWidth),\n false\n )\n this.ctx.stroke()\n currentSubDivision = currentDivision + subDivisions\n currentDivision += rangeDivisions\n if (t !== ticksOptions.divisions && subdivisionCount > 0) {\n results.push(\n function() {\n var k, ref1, results1\n results1 = []\n for (\n st = k = 0, ref1 = subdivisionCount - 1;\n k < ref1;\n st = k += 1\n ) {\n this.ctx.lineWidth = this.lineWidth * subLength\n scaleMutate = (this.lineWidth / 2) * (1 - subLength)\n tmpRadius =\n this.radius * this.options.radiusScale + scaleMutate\n this.ctx.strokeStyle = subColor\n this.ctx.beginPath()\n this.ctx.arc(\n 0,\n 0,\n tmpRadius,\n this.getAngle(currentSubDivision - subWidth),\n this.getAngle(currentSubDivision + subWidth),\n false\n )\n this.ctx.stroke()\n results1.push((currentSubDivision += subDivisions))\n }\n return results1\n }.call(this)\n )\n } else {\n results.push(void 0)\n }\n }\n return results\n }\n }\n\n Gauge.prototype.render = function() {\n var displayedAngle,\n fillStyle,\n gauge,\n h,\n j,\n k,\n len,\n len1,\n max,\n min,\n radius,\n ref,\n ref1,\n scaleMutate,\n tmpRadius,\n w,\n zone\n w = this.canvas.width / 2\n h =\n this.canvas.height * this.paddingTop +\n this.availableHeight -\n (this.radius + this.lineWidth / 2) * this.extraPadding\n displayedAngle = this.getAngle(this.displayedValue)\n if (this.textField) {\n this.textField.render(this)\n }\n this.ctx.lineCap = 'butt'\n radius = this.radius * this.options.radiusScale\n if (this.options.staticLabels) {\n this.renderStaticLabels(this.options.staticLabels, w, h, radius)\n }\n if (this.options.staticZones) {\n this.ctx.save()\n this.ctx.translate(w, h)\n this.ctx.lineWidth = this.lineWidth\n ref = this.options.staticZones\n for (j = 0, len = ref.length; j < len; j++) {\n zone = ref[j]\n min = zone.min\n if (this.options.limitMin && min < this.minValue) {\n min = this.minValue\n }\n max = zone.max\n if (this.options.limitMax && max > this.maxValue) {\n max = this.maxValue\n }\n tmpRadius = this.radius * this.options.radiusScale\n if (zone.height) {\n this.ctx.lineWidth = this.lineWidth * zone.height\n scaleMutate =\n (this.lineWidth / 2) * (zone.offset || 1 - zone.height)\n tmpRadius = this.radius * this.options.radiusScale + scaleMutate\n }\n this.ctx.strokeStyle = zone.strokeStyle\n this.ctx.beginPath()\n this.ctx.arc(\n 0,\n 0,\n tmpRadius,\n this.getAngle(min),\n this.getAngle(max),\n false\n )\n this.ctx.stroke()\n }\n } else {\n if (this.options.customFillStyle !== void 0) {\n fillStyle = this.options.customFillStyle(this)\n } else if (this.percentColors !== null) {\n fillStyle = this.getColorForValue(\n this.displayedValue,\n this.options.generateGradient\n )\n } else if (this.options.colorStop !== void 0) {\n if (this.options.gradientType === 0) {\n fillStyle = this.ctx.createRadialGradient(w, h, 9, w, h, 70)\n } else {\n fillStyle = this.ctx.createLinearGradient(0, 0, w, 0)\n }\n fillStyle.addColorStop(0, this.options.colorStart)\n fillStyle.addColorStop(1, this.options.colorStop)\n } else {\n fillStyle = this.options.colorStart\n }\n this.ctx.strokeStyle = fillStyle\n this.ctx.beginPath()\n this.ctx.arc(\n w,\n h,\n radius,\n (1 + this.options.angle) * Math.PI,\n displayedAngle,\n false\n )\n this.ctx.lineWidth = this.lineWidth\n this.ctx.stroke()\n this.ctx.strokeStyle = this.options.strokeColor\n this.ctx.beginPath()\n this.ctx.arc(\n w,\n h,\n radius,\n displayedAngle,\n (2 - this.options.angle) * Math.PI,\n false\n )\n this.ctx.stroke()\n this.ctx.save()\n this.ctx.translate(w, h)\n }\n if (this.options.renderTicks) {\n this.renderTicks(this.options.renderTicks, w, h, radius)\n }\n this.ctx.restore()\n this.ctx.translate(w, h)\n ref1 = this.gp\n for (k = 0, len1 = ref1.length; k < len1; k++) {\n gauge = ref1[k]\n gauge.update(true)\n }\n return this.ctx.translate(-w, -h)\n }\n\n return Gauge\n })(BaseGauge)\n\n BaseDonut = (function(superClass) {\n extend(BaseDonut, superClass)\n\n BaseDonut.prototype.lineWidth = 15\n\n BaseDonut.prototype.displayedValue = 0\n\n BaseDonut.prototype.value = 33\n\n BaseDonut.prototype.maxValue = 80\n\n BaseDonut.prototype.minValue = 0\n\n BaseDonut.prototype.options = {\n lineWidth: 0.1,\n colorStart: '#6f6ea0',\n colorStop: '#c0c0db',\n strokeColor: '#eeeeee',\n shadowColor: '#d5d5d5',\n angle: 0.35,\n radiusScale: 1.0,\n }\n\n function BaseDonut(canvas) {\n this.canvas = canvas\n BaseDonut.__super__.constructor.call(this)\n if (typeof G_vmlCanvasManager !== 'undefined') {\n this.canvas = window.G_vmlCanvasManager.initElement(this.canvas)\n }\n this.ctx = this.canvas.getContext('2d')\n this.setOptions()\n this.render()\n }\n\n BaseDonut.prototype.getAngle = function(value) {\n return (\n (1 - this.options.angle) * Math.PI +\n ((value - this.minValue) / (this.maxValue - this.minValue)) *\n (2 + this.options.angle - (1 - this.options.angle)) *\n Math.PI\n )\n }\n\n BaseDonut.prototype.setOptions = function(options) {\n if (options == null) {\n options = null\n }\n BaseDonut.__super__.setOptions.call(this, options)\n this.lineWidth = this.canvas.height * this.options.lineWidth\n this.radius =\n this.options.radiusScale * (this.canvas.height / 2 - this.lineWidth / 2)\n return this\n }\n\n BaseDonut.prototype.set = function(value) {\n this.value = this.parseValue(value)\n if (this.value > this.maxValue) {\n if (this.options.limitMax) {\n this.value = this.maxValue\n } else {\n this.maxValue = this.value\n }\n } else if (this.value < this.minValue) {\n if (this.options.limitMin) {\n this.value = this.minValue\n } else {\n this.minValue = this.value\n }\n }\n AnimationUpdater.run(this.forceUpdate)\n return (this.forceUpdate = false)\n }\n\n BaseDonut.prototype.render = function() {\n var displayedAngle, grdFill, h, start, stop, w\n displayedAngle = this.getAngle(this.displayedValue)\n w = this.canvas.width / 2\n h = this.canvas.height / 2\n if (this.textField) {\n this.textField.render(this)\n }\n grdFill = this.ctx.createRadialGradient(w, h, 39, w, h, 70)\n grdFill.addColorStop(0, this.options.colorStart)\n grdFill.addColorStop(1, this.options.colorStop)\n start = this.radius - this.lineWidth / 2\n stop = this.radius + this.lineWidth / 2\n this.ctx.strokeStyle = this.options.strokeColor\n this.ctx.beginPath()\n this.ctx.arc(\n w,\n h,\n this.radius,\n (1 - this.options.angle) * Math.PI,\n (2 + this.options.angle) * Math.PI,\n false\n )\n this.ctx.lineWidth = this.lineWidth\n this.ctx.lineCap = 'round'\n this.ctx.stroke()\n this.ctx.strokeStyle = grdFill\n this.ctx.beginPath()\n this.ctx.arc(\n w,\n h,\n this.radius,\n (1 - this.options.angle) * Math.PI,\n displayedAngle,\n false\n )\n return this.ctx.stroke()\n }\n\n return BaseDonut\n })(BaseGauge)\n\n Donut = (function(superClass) {\n extend(Donut, superClass)\n\n function Donut() {\n return Donut.__super__.constructor.apply(this, arguments)\n }\n\n Donut.prototype.strokeGradient = function(w, h, start, stop) {\n var grd\n grd = this.ctx.createRadialGradient(w, h, start, w, h, stop)\n grd.addColorStop(0, this.options.shadowColor)\n grd.addColorStop(0.12, this.options._orgStrokeColor)\n grd.addColorStop(0.88, this.options._orgStrokeColor)\n grd.addColorStop(1, this.options.shadowColor)\n return grd\n }\n\n Donut.prototype.setOptions = function(options) {\n var h, start, stop, w\n if (options == null) {\n options = null\n }\n Donut.__super__.setOptions.call(this, options)\n w = this.canvas.width / 2\n h = this.canvas.height / 2\n start = this.radius - this.lineWidth / 2\n stop = this.radius + this.lineWidth / 2\n this.options._orgStrokeColor = this.options.strokeColor\n this.options.strokeColor = this.strokeGradient(w, h, start, stop)\n return this\n }\n\n return Donut\n })(BaseDonut)\n\n window.AnimationUpdater = {\n elements: [],\n animId: null,\n addAll: function(list) {\n var elem, j, len, results\n results = []\n for (j = 0, len = list.length; j < len; j++) {\n elem = list[j]\n results.push(AnimationUpdater.elements.push(elem))\n }\n return results\n },\n add: function(object) {\n return AnimationUpdater.elements.push(object)\n },\n run: function(force) {\n var elem, finished, isCallback, j, len, ref\n if (force == null) {\n force = false\n }\n isCallback = isFinite(parseFloat(force))\n if (isCallback || force === true) {\n finished = true\n ref = AnimationUpdater.elements\n for (j = 0, len = ref.length; j < len; j++) {\n elem = ref[j]\n if (elem.update(force === true)) {\n finished = false\n }\n }\n return (AnimationUpdater.animId = finished\n ? null\n : requestAnimationFrame(AnimationUpdater.run))\n } else if (force === false) {\n if (AnimationUpdater.animId === !null) {\n cancelAnimationFrame(AnimationUpdater.animId)\n }\n return (AnimationUpdater.animId = requestAnimationFrame(\n AnimationUpdater.run\n ))\n }\n },\n }\n\n if (typeof window.define === 'function' && window.define.amd != null) {\n define(function() {\n return {\n Gauge: Gauge,\n Donut: Donut,\n BaseDonut: BaseDonut,\n TextRenderer: TextRenderer,\n }\n })\n } else if (typeof module !== 'undefined' && module.exports != null) {\n module.exports = {\n Gauge: Gauge,\n Donut: Donut,\n BaseDonut: BaseDonut,\n TextRenderer: TextRenderer,\n }\n } else {\n window.Gauge = Gauge\n window.Donut = Donut\n window.BaseDonut = BaseDonut\n window.TextRenderer = TextRenderer\n }\n}.call(this))\n","/*\n *\n * SolutionCenter constants\n *\n */\n\nexport const LOAD_SOLUTION_CARDS = 'app/SoltuionCenter/LOAD_SOLUTION_CARDS'\nexport const GET_SOLUTION_CARDS = 'app/SolutionCenter/GET_SOLUTION_CARDS'\nexport const ADD_SC_WIDGET = 'app/SolutionCenter/ADD_SC_WIDGET'\nexport const TOGGLE_SC_WIDGET_STATIC = 'app/SolutionCenter/TOGGLE_WIDGET_STATIC'\nexport const REMOVE_SC_WIDGET = 'app/SolutionCenter/REMOVE_SC_WIDGET'\nexport const HANDLE_TAB_EDIT = 'app/SolutionCenter/HANDLE_TAB_EDIT'\nexport const HANDLE_TAB_SEQ_CHANGE = 'app/SolutionCenter/HANDLE_TAB_SEQ_CHANGE'\nexport const HANDLE_SC_ADD = 'app/SolutionCenter/HANDLE_TAB_ADD'\nexport const DELETE_SOLUTION_CARD = 'app/SolutionCenter/DELETE_SOLUTION_CARD'\nexport const UPDATE_WIDGET = 'app/SolutionCenter/UPDATE_WIDGET'\nexport const UPDATE_WIDGET_SUCCESS = 'app/SolutionCenter/UPDATE_WIDGET_SUCCESS'\nexport const SET_WIDGET_OPTIONS = 'app/SolutionCenter/SET_WIDGET_OPTIONS'\nexport const SAVE_LAYOUT = 'app/SolutionCenter/SAVE_LAYOUT'\nexport const SAVE_LAYOUT_SUCCESS = 'app/SolutionCenter/SAVE_LAYOUT_SUCCESS'\nexport const REMOVE_WIDGET_SUCCESS = 'app/SolutionCenter/REMOVE_WIDGET_SUCCESS'\nexport const ADD_SOLUTION_CARD_SUCCESS =\n 'app/SolutionCenter/ADD_SOLUTION_CARD_SUCCESS'\nexport const DELETE_SOLUTION_CARD_SUCCESS =\n 'app/SolutionCenter/DELETE_SOLUTION_CARD_SUCCESS'\nexport const ADD_WIDGET_SUCCESS = 'app/SolutionCenter/ADD_WIDGET_SUCCESS'\nexport const UPDATE_EDIT_WIDGET = 'app/SolutionCenter/UPDATE_EDIT_WIDGET'\nexport const UPDATE_SOLUTION_CARD = 'app/SolutionCenter/UPDATE_SOLUTION_CARD'\nexport const UPDATE_SOLUTION_CARD_SUCCESS =\n 'app/SolutionCenter/UPDATE_SOLUTION_CARD_SUCCESS'\nexport const START_UPDATE_SC_POLL = 'app/SolutionCenter/START_UPDATE_SC_POLL'\nexport const STOP_UPDATE_SC_POLL = 'app/SolutionCenter/STOP_UPDATE_SC_POLL'\nexport const SET_SHOW_CREATE_WIDGET_MODAL =\n 'app/SolutionCenter/SET_SHOW_CREATE_WIDGET_MODAL'\nexport const COPY_SOLUTION_CARD = 'app/SolutionCenter/COPY_SOLUTION_CARD'\nexport const COPY_SOLUTION_CARD_SUCCESS =\n 'app/SolutionCenter/COPY_SOLUTION_CARD_SUCCESS'\n","import { fromJS } from 'immutable'\nimport { handleActions } from 'redux-actions'\nimport moment from 'moment'\n\nimport {\n HANDLE_SC_ADD,\n ADD_SOLUTION_CARD_SUCCESS,\n SAVE_LAYOUT,\n SAVE_LAYOUT_SUCCESS,\n TOGGLE_SC_WIDGET_STATIC,\n UPDATE_SOLUTION_CARD,\n UPDATE_SOLUTION_CARD_SUCCESS,\n SET_WIDGET_OPTIONS,\n UPDATE_WIDGET,\n} from 'containers/SolutionCenter/constants'\n\nconst initialState = fromJS({\n savingLayout: false,\n addingSolutionCard: false,\n hasUnsavedChanges: false,\n lastSaved: null,\n savingScEdits: false,\n})\n\nconst solutionCenterCardsReducer = handleActions(\n {\n [ADD_SOLUTION_CARD_SUCCESS]: state =>\n state.set('addingSolutionCard', false),\n [HANDLE_SC_ADD]: state => state.set('addingSolutionCard', true),\n [SAVE_LAYOUT]: state => state.set('savingLayout', true),\n [SAVE_LAYOUT_SUCCESS]: state =>\n state\n .set('savingLayout', false)\n .set('hasUnsavedChanges', false)\n .set('lastSavedLayout', moment()),\n [SET_WIDGET_OPTIONS]: state => state.set('hasUnsavedChanges', true),\n [TOGGLE_SC_WIDGET_STATIC]: state => state.set('hasUnsavedChanges', true),\n [UPDATE_SOLUTION_CARD]: state => state.set('savingScEdits', true),\n [UPDATE_SOLUTION_CARD_SUCCESS]: state => state.set('savingScEdits', false),\n [UPDATE_WIDGET]: state => state.set('hasUnsavedChanges', true),\n },\n initialState\n)\n\nexport default solutionCenterCardsReducer\n","/*\n *\n * SolutionCenter actions\n *\n */\n\nimport {\n LOAD_SOLUTION_CARDS,\n ADD_SC_WIDGET,\n TOGGLE_SC_WIDGET_STATIC,\n REMOVE_SC_WIDGET,\n HANDLE_TAB_EDIT,\n HANDLE_TAB_SEQ_CHANGE,\n HANDLE_SC_ADD,\n GET_SOLUTION_CARDS,\n DELETE_SOLUTION_CARD,\n UPDATE_WIDGET,\n UPDATE_WIDGET_SUCCESS,\n UPDATE_EDIT_WIDGET,\n SET_WIDGET_OPTIONS,\n SAVE_LAYOUT,\n SAVE_LAYOUT_SUCCESS,\n REMOVE_WIDGET_SUCCESS,\n ADD_SOLUTION_CARD_SUCCESS,\n DELETE_SOLUTION_CARD_SUCCESS,\n ADD_WIDGET_SUCCESS,\n UPDATE_SOLUTION_CARD,\n UPDATE_SOLUTION_CARD_SUCCESS,\n START_UPDATE_SC_POLL,\n STOP_UPDATE_SC_POLL,\n SET_SHOW_CREATE_WIDGET_MODAL,\n COPY_SOLUTION_CARD,\n COPY_SOLUTION_CARD_SUCCESS,\n} from './constants'\nimport { createAction } from 'redux-actions'\n\nexport const updateWidget = createAction(UPDATE_WIDGET, (cardId, widget) => ({\n cardId,\n widget,\n}))\n\nexport const setWidgetOptions = createAction(\n SET_WIDGET_OPTIONS,\n (cardId, widgetId, options) => ({\n cardId,\n widgetId,\n options,\n })\n)\nexport const deleteSolutionCard = createAction(DELETE_SOLUTION_CARD, srn => ({\n srn,\n}))\nexport const getSolutionCards = createAction(GET_SOLUTION_CARDS)\nexport const handleTabEdit = createAction(\n HANDLE_TAB_EDIT,\n ({ type, index }) => ({\n type,\n index,\n })\n)\nexport const handleTabSeqChange = createAction(\n HANDLE_TAB_SEQ_CHANGE,\n ({ oldIndex, newIndex }) => ({\n oldIndex,\n newIndex,\n })\n)\nexport const handleSCAdd = createAction(HANDLE_SC_ADD, name => ({\n name,\n}))\n\nexport const loadSolutionCards = createAction(LOAD_SOLUTION_CARDS, cards => ({\n cards,\n}))\nexport const addSCWidget = createAction(ADD_SC_WIDGET, (card, widget) => ({\n card,\n widget,\n}))\nexport const toggleSCWidgetStatic = createAction(\n TOGGLE_SC_WIDGET_STATIC,\n (cardId, widgetId) => ({\n cardId,\n widgetId,\n })\n)\nexport const removeSCWidget = createAction(REMOVE_SC_WIDGET, (cardId, srn) => ({\n cardId,\n srn,\n}))\n\nexport const saveLayout = createAction(SAVE_LAYOUT, card => ({ card }))\nexport const saveLayoutSuccess = createAction(SAVE_LAYOUT_SUCCESS)\nexport const removeWidgetSuccess = createAction(REMOVE_WIDGET_SUCCESS)\nexport const addSolutionCardSuccess = createAction(ADD_SOLUTION_CARD_SUCCESS)\nexport const deleteSolutionCardSuccess = createAction(\n DELETE_SOLUTION_CARD_SUCCESS\n)\nexport const addWidgetSuccess = createAction(ADD_WIDGET_SUCCESS)\n\nexport const updateEditWidget = createAction(\n UPDATE_EDIT_WIDGET,\n (card, widget) => ({\n card,\n widget,\n })\n)\n\nexport const updateWidgetSuccess = createAction(UPDATE_WIDGET_SUCCESS)\nexport const updateSolutionCard = createAction(UPDATE_SOLUTION_CARD)\nexport const updateSolutionCardSuccess = createAction(\n UPDATE_SOLUTION_CARD_SUCCESS\n)\nexport const startUpdateScPoll = createAction(START_UPDATE_SC_POLL)\nexport const stopUpdateScPoll = createAction(STOP_UPDATE_SC_POLL)\nexport const setShowCreateWidgetModal = createAction(\n SET_SHOW_CREATE_WIDGET_MODAL\n)\nexport const copySolutionCard = createAction(COPY_SOLUTION_CARD)\nexport const copySolutionCardSuccess = createAction(COPY_SOLUTION_CARD_SUCCESS)\n","import { createSelector } from 'reselect'\nimport { List } from 'immutable'\n\nexport const selectSolutionCenterDomain = state => state.get('solutionCenter')\n\nexport const selectCards = createSelector(\n selectSolutionCenterDomain,\n solutionCenter => solutionCenter.get('solutionCards', List())\n)\n\nexport const selectShowCreateWidgetModal = createSelector(\n selectSolutionCenterDomain,\n solutionCenter => {\n return solutionCenter.get('showCreateWidgetModal')\n }\n)\n\nexport const selectIsScCopySaving = createSelector(\n selectSolutionCenterDomain,\n solutionCenter => solutionCenter.get('copySolutionCardSaving')\n)\n","import { createSelector } from 'reselect'\nimport qs from 'query-string'\nimport { fromJS, List } from 'immutable'\nimport { Map } from 'immutable'\n\nimport { selectCards } from 'containers/SolutionCenter/selectors'\nimport { sortCards } from 'utils/sonraiUtils'\nimport { selectUserProfile } from 'containers/UserProfileData/selectors'\n\nconst selectSolutionCenterCardsDomain = state =>\n state.get('solutionCenterCards') || Map()\n\nexport const selectAddingSolutionCard = createSelector(\n selectSolutionCenterCardsDomain,\n state => state.get('addingSolutionCard')\n)\n\nexport const selectLastSaved = createSelector(\n selectSolutionCenterCardsDomain,\n state => {\n return state.get('lastSaved')\n }\n)\n\nexport const selectHasUnsavedChanges = createSelector(\n selectSolutionCenterCardsDomain,\n solutionCenter => solutionCenter.get('hasUnsavedChanges', false)\n)\n\nexport const selectSavingScEdits = createSelector(\n selectSolutionCenterCardsDomain,\n solutionCenter => solutionCenter.get('savingScEdits', false)\n)\n\nexport const selectSavingLayout = createSelector(\n selectSolutionCenterCardsDomain,\n solutionCenter => solutionCenter.get('savingLayout', false)\n)\n\nexport const selectSortedCards = createSelector(\n [selectCards, selectUserProfile],\n (cards, userProfile) => {\n const hiddenCards = userProfile.get('hiddenSolutionCards', List())\n\n const visibleCards = cards.filterNot(card =>\n hiddenCards.includes(card.get('srn'))\n )\n\n return fromJS(sortCards(visibleCards.toJS()))\n }\n)\n\nexport const selectSortedCardIds = createSelector(\n [selectSortedCards],\n cards => cards.map(card => card.get('srn'))\n)\n\nexport const selectUrlTabId = state => {\n const search = state.getIn(['router', 'location', 'search']) || ''\n return qs.parse(search).tabId\n}\n\nexport const selectSelectedCardIndex = createSelector(\n [selectUrlTabId, selectSortedCards],\n (tabId, cards) => {\n if (tabId) {\n const cardIndex = cards.findIndex(card => card.get('srn') === tabId)\n if (cardIndex < 0) {\n return 0\n } else {\n return cardIndex\n }\n } else {\n return 0\n }\n }\n)\n\nexport const selectSelectedCard = createSelector(\n [selectSortedCards, selectSelectedCardIndex],\n (cards, index) => cards.get(index, Map())\n)\n\nexport const selectSelectedCardSrn = createSelector(\n selectSelectedCard,\n card => card.get('srn')\n)\n\nexport const selectSelectedCardName = createSelector(\n selectSelectedCard,\n card => card.get('name')\n)\n","import React from 'react'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport { Map } from 'immutable'\nimport { connect } from 'react-redux'\nimport { createStructuredSelector } from 'reselect'\nimport { compose, bindActionCreators } from 'redux'\n\nimport TabBar from 'components/TabBar'\nimport ImmutablePureComponent from 'components/ImmutablePureComponent'\nimport { selectUserProfile } from 'containers/UserProfileData/selectors'\nimport { handleTabSeqChange } from 'containers/SolutionCenter/actions'\n\nimport {\n selectSortedCards,\n selectSelectedCard,\n selectSelectedCardIndex,\n} from './selectors'\n\nexport class SolutionCenterTabs extends ImmutablePureComponent {\n getTabs = () => {\n return this.props.solutionCards\n .map(card => {\n return Map({\n id: card.get('srn'),\n text: card.get('name'),\n date: card.get('createdDate'),\n })\n })\n .toJS()\n }\n\n handleChangeTab = index => {\n const selectedSolutionCard = this.props.solutionCards.get(index) || Map()\n this.props.setSolutionCard(selectedSolutionCard.get('srn'))\n }\n\n render() {\n if (this.props.solutionCards.isEmpty()) {\n return null\n }\n\n return (\n \n )\n }\n}\n\nSolutionCenterTabs.propTypes = {\n solutionCards: ImmutablePropTypes.list,\n}\n\nconst mapStateToProps = createStructuredSelector({\n userProfile: selectUserProfile,\n selectedSolutionCard: selectSelectedCard,\n selectedCardIndex: selectSelectedCardIndex,\n solutionCards: selectSortedCards,\n})\n\nfunction mapDispatchToProps(dispatch) {\n return bindActionCreators(\n {\n handleTabSeqChange,\n },\n dispatch\n )\n}\n\nconst withConnect = connect(\n mapStateToProps,\n mapDispatchToProps\n)\n\nexport default compose(withConnect)(SolutionCenterTabs)\n","import { defineMessages } from 'react-intl'\n\nexport default defineMessages({\n header: {\n id: 'app.containers.AddSolutionCardModal.header',\n defaultMessage: 'Create Solution Card',\n },\n nameLabel: {\n id: 'app.containers.AddSolutionCardModal.nameLabel',\n defaultMessage: 'Name',\n },\n titlePlaceholder: {\n id: 'app.containers.AddSolutionCardModal.titlePlaceholder',\n defaultMessage: 'E.g. Identities',\n },\n titlePlaceholderErr: {\n id: 'app.containers.AddSolutionCardModal.titlePlaceholder',\n defaultMessage: 'Please Enter A Name...',\n },\n createButton: {\n id: 'app.containers.AddSolutionCardModal.createButton',\n defaultMessage: 'Create',\n },\n cancelButton: {\n id: 'app.containers.AddSolutionCardModal.cancelButton',\n defaultMessage: 'Cancel',\n },\n noDup: {\n id: 'app.containers.AddSolutionCardModal.noDup',\n defaultMessage: 'This card name already exists.',\n },\n})\n","import React from 'react'\nimport PropTypes from 'prop-types'\nimport { connect } from 'react-redux'\nimport { FormattedMessage, injectIntl, intlShape } from 'react-intl'\nimport { createStructuredSelector } from 'reselect'\nimport { compose, bindActionCreators } from 'redux'\nimport { Modal, ModalHeader, ModalBody, ModalFooter, Input } from 'reactstrap'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport Button from 'components/Button'\nimport TextLink from 'components/TextLink'\nimport Icon from 'components/Icon'\nimport FormLabel from 'components/FormLabel'\nimport { handleSCAdd } from 'containers/SolutionCenter/actions'\n\nimport { selectAddingSolutionCard, selectSortedCards } from './selectors'\nimport messages from './messages'\n\nexport class ModalAddSolutionCard extends React.Component {\n state = {\n title: '',\n showMessage: false,\n }\n\n styles = {\n description: {\n fontSize: '0.9em',\n },\n }\n\n componentDidUpdate(oldProps) {\n if (oldProps.addingSolutionCard && !this.props.addingSolutionCard) {\n this.props.toggle()\n this.setState({\n title: '',\n })\n }\n }\n\n setTitle = e => {\n this.setState({\n title: e.target.value,\n showMessage: false,\n })\n }\n\n handleReturn = key => {\n if (key === 13) {\n if (this.state.title !== '') {\n this.props.handleSCAdd(this.state.title)\n }\n }\n }\n\n handleSCAdd = () => {\n const title =\n this.state.title !== ''\n ? this.state.title.toLowerCase()\n : this.state.title\n const sortedCardNames = this.props.sortedCards.map(card =>\n card.get('name').toLowerCase()\n )\n if (title !== '' && !sortedCardNames.includes(title)) {\n this.props.handleSCAdd(this.state.title)\n } else {\n this.setState({ showMessage: true })\n }\n }\n\n render() {\n return (\n \n \n \n \n \n \n Solution Cards enable you to create a visual representation of\n information or details about your cloud account.\n
\n \n Widgets on the Solution Card display the results of saved searches,\n or framework status and summary information from the Control Center\n
\n \n \n \n this.handleReturn(e.keyCode)}\n />\n \n \n {this.state.showMessage && (\n this.setState({ title: '' })}\n >\n \n \n )}\n \n \n \n \n \n
\n \n \n )\n }\n}\n\nModalAddSolutionCard.propTypes = {\n addingSolutionCard: PropTypes.bool,\n handleSCAdd: PropTypes.func.isRequired,\n intl: intlShape,\n isOpen: PropTypes.bool,\n toggle: PropTypes.func.isRequired,\n sortedCards: ImmutablePropTypes.iterable,\n}\n\nconst mapStateToProps = createStructuredSelector({\n addingSolutionCard: selectAddingSolutionCard,\n sortedCards: selectSortedCards,\n})\n\nfunction mapDispatchToProps(dispatch) {\n return bindActionCreators(\n {\n handleSCAdd,\n },\n dispatch\n )\n}\n\nconst withConnect = connect(\n mapStateToProps,\n mapDispatchToProps\n)\n\nexport default compose(\n withConnect,\n injectIntl\n)(ModalAddSolutionCard)\n","export const TOOLBAR_HEIGHT = 51\n","import React, { Fragment } from 'react'\nimport PropTypes from 'prop-types'\nimport { connect } from 'react-redux'\nimport { createStructuredSelector } from 'reselect'\nimport { compose, bindActionCreators } from 'redux'\nimport qs from 'query-string'\nimport { push } from 'connected-react-router'\nimport { withRouter } from 'react-router'\n\nimport injectReducer from 'utils/injectReducer'\nimport WithPermission from 'containers/PermissionChecker/WithPermission'\nimport BorderlessButton from 'components/BorderlessButton'\nimport Icon from 'components/Icon'\nimport IHelp from 'containers/IHelp'\n\nimport reducer from './reducer'\nimport SolutionCenterTabs from './SolutionCenterTabs'\nimport ModalAddSolutionCard from './ModalAddSolutionCard'\nimport { TOOLBAR_HEIGHT } from './constants'\n\nexport class SolutionCenterCards extends React.Component {\n styles = {\n toolbar: {\n display: 'grid',\n background: '#FFFFFF',\n borderBottom: '1px solid #C0C0C0',\n fontSize: '30px',\n height: `${TOOLBAR_HEIGHT}px`,\n gridTemplateColumns: `1fr auto`,\n gridTemplateAreas: '\"tabs actions\"',\n },\n tabs: {\n gridArea: 'tabs',\n },\n actions: {\n gridArea: 'actions',\n },\n toolbarButton: {\n padding: '0 0.5em',\n },\n }\n\n state = {\n showAddSCModal: false,\n }\n\n toggleAddSCModal = () => {\n this.setState(currentState => ({\n showAddSCModal: !currentState.showAddSCModal,\n }))\n }\n\n setSolutionCard = selectedSolutionCardId => {\n const { push } = this.props\n\n push({\n search: qs.stringify({\n tabId: selectedSolutionCardId,\n }),\n })\n }\n\n render() {\n return (\n \n \n \n
\n\n
\n `/org/${org}/`}\n >\n \n Add Card\n \n \n \n \n \n
\n
\n
\n \n )\n }\n}\n\nSolutionCenterCards.propTypes = {\n push: PropTypes.func.isRequired,\n}\n\nconst mapStateToProps = createStructuredSelector({})\n\nfunction mapDispatchToProps(dispatch) {\n return bindActionCreators(\n {\n push,\n },\n dispatch\n )\n}\n\nconst withConnect = connect(mapStateToProps, mapDispatchToProps)\n\nconst withReducer = injectReducer({ key: 'solutionCenterCards', reducer })\n\nexport default compose(\n withReducer,\n withConnect,\n withRouter\n)(SolutionCenterCards)\n","import { createSelector } from 'reselect'\nimport { List, Map } from 'immutable'\n\nimport { selectSavedSearches } from 'containers/SonraiData/selectors'\n\nexport const selectWidgetModalDomain = state => state.get('WidgetModal')\n\nexport const selectWidgetType = createSelector(\n selectWidgetModalDomain,\n state => state.get('widgetType')\n)\n\nexport const selectWidgetTitle = createSelector(\n selectWidgetModalDomain,\n state => state.get('widgetTitle')\n)\n\nexport const selectWidgetSubTitle = createSelector(\n selectWidgetModalDomain,\n state => state.get('widgetSubTitle')\n)\n\nexport const selectWidgetSize = createSelector(\n selectWidgetModalDomain,\n state => state.get('widgetSize', List()).toJS()\n)\n\nexport const selectWidgetSelector = createSelector(\n selectWidgetModalDomain,\n state => state.get('widgetSelector')\n)\n\nexport const selectWidgetResultLayout = createSelector(\n selectWidgetModalDomain,\n state => state.get('widgetResultLayout', Map()).toJS()\n)\n\nexport const selectWidgetPreviewData = createSelector(\n selectWidgetModalDomain,\n state => state.get('widgetPreviewData')\n)\n\nexport const selectWidgetOptions = createSelector(\n selectWidgetModalDomain,\n state => state.get('widgetOptions', Map()).toJS()\n)\n\nexport const selectSearchCardsBySearchId = createSelector(\n selectSavedSearches,\n savedSearches =>\n savedSearches.map(search =>\n search.getIn(['query', 'fields'], Map()).toList()\n )\n)\n\nexport const selectPreviewWidget = createSelector(\n selectWidgetModalDomain,\n state => state.get('previewWidget')\n)\n","/*\n *\n * WidgetModal reducer\n *\n */\n\nimport { fromJS, Map } from 'immutable'\nimport {\n LOAD_WIDGET,\n SET_WIDGET_TITLE,\n SET_WIDGET_SUB_TITLE,\n SET_WIDGET_TYPE,\n SET_WIDGET_SELECTOR,\n SET_WIDGET_FORMATTER,\n SET_WIDGET_SEARCH_FIELD,\n SET_WIDGET_SAVED_SEARCH,\n CLEAR_WIDGET_MODAL,\n SET_WIDGET_SIZE,\n SET_WIDGET_OPTIONS,\n SET_WIDGET_SONRAI_SAVED_SEARCH,\n TOGGLE_PREVIEW_WIDGET,\n} from './constants'\nimport { handleActions } from 'redux-actions'\n\nconst initialState = fromJS({\n widgetFormatter: null,\n widgetResultLayout: {\n indexedSearches: {},\n searchCards: {},\n widgetOptions: {},\n },\n widgetSelector: '',\n widgetSize: [],\n widgetSubTitle: '',\n widgetTitle: '',\n widgetType: null,\n widgetOptions: {\n sonraiSearches: {},\n },\n previewWidget: false,\n})\n\nconst WidgetModalReducer = handleActions(\n {\n [CLEAR_WIDGET_MODAL]: () => initialState,\n [SET_WIDGET_SEARCH_FIELD]: (state, { payload }) => {\n const widgetIndex = payload.index ? payload.index : 'search'\n const searchId = state.getIn([\n 'widgetResultLayout',\n 'indexedSearches',\n widgetIndex,\n ])\n const savedSearch = payload.savedSearches.find(\n search => search.get('sid') === searchId,\n null,\n Map()\n )\n if (payload.field) {\n return state.setIn(\n ['widgetResultLayout', 'searchCards', widgetIndex],\n savedSearch.getIn(['query', 'fields', payload.field], Map())\n )\n } else {\n return state.deleteIn([\n 'widgetResultLayout',\n 'searchCards',\n widgetIndex,\n ])\n }\n },\n [SET_WIDGET_SAVED_SEARCH]: (state, { payload }) => {\n if (payload.search) {\n return state.setIn(\n [\n 'widgetResultLayout',\n 'indexedSearches',\n payload.index ? payload.index : 'search',\n ],\n payload.search\n )\n } else {\n return state.deleteIn([\n 'widgetResultLayout',\n 'indexedSearches',\n payload.index ? payload.index : 'search',\n ])\n }\n },\n [LOAD_WIDGET]: (state, { payload }) => {\n return state\n .set('widgetType', payload.get('type'))\n .set('widgetTitle', payload.get('title'))\n .set('widgetSubTitle', payload.get('subtitle'))\n .set('widgetResultLayout', payload.get('resultLayout'))\n .set('widgetOptions', payload.get('options'))\n .set('widgetSize', payload.get('widgetSize'))\n },\n [SET_WIDGET_SONRAI_SAVED_SEARCH]: (state, { payload }) => {\n if (payload.search) {\n return state.setIn(\n [\n 'widgetOptions',\n 'sonraiSearches',\n payload.index ? payload.index : 'search',\n ],\n payload.search\n )\n } else {\n return state.deleteIn([\n 'widgetOptions',\n 'sonraiSearches',\n payload.index ? payload.index : 'search',\n ])\n }\n },\n [SET_WIDGET_OPTIONS]: (state, { payload }) => {\n return state.mergeIn(\n ['widgetResultLayout', 'widgetOptions'],\n fromJS(payload)\n )\n },\n [SET_WIDGET_TITLE]: (state, { payload }) =>\n state.set('widgetTitle', payload.input),\n [SET_WIDGET_SUB_TITLE]: (state, { payload }) => {\n return state.set('widgetSubTitle', payload.input)\n },\n [SET_WIDGET_TYPE]: (state, { payload }) => {\n return state.set('widgetType', payload.input)\n },\n [SET_WIDGET_SIZE]: (state, { payload }) =>\n state.set('widgetSize', fromJS(payload)),\n [SET_WIDGET_SELECTOR]: (state, { payload }) => {\n return state.set('widgetSelector', payload.input)\n },\n [SET_WIDGET_FORMATTER]: (state, { payload }) => {\n return state.set('widgetFormatter', payload.input)\n },\n [TOGGLE_PREVIEW_WIDGET]: state => {\n return state.set('previewWidget', !state.get('previewWidget'))\n },\n },\n initialState\n)\n\nexport default WidgetModalReducer\n","/*\n *\n * WidgetModal constants\n *\n */\n\nexport const SET_WIDGET_TITLE = 'app/WidgetModal/SET_WIDGET_NAME'\nexport const SET_WIDGET_SUB_TITLE = 'app/WidgetModal/SET_WIDGET_SUB_TITLE'\nexport const SET_WIDGET_TYPE = 'app/WidgetModal/SET_WIDGET_TYPE'\nexport const SET_WIDGET_SELECTOR = 'app/WidgetModal/SET_WIDGET_SELECTOR'\nexport const SET_WIDGET_FORMATTER = 'app/WidgetModal/SET_WIDGET_FORMATTER'\nexport const SET_WIDGET_SEARCH_FIELD = 'app/WidgetModal/SET_WIDGET_SEARCH_FIELD'\nexport const SET_WIDGET_SAVED_SEARCH = 'app/WidgetModal/SET_WIDGET_SAVED_SEARCH'\nexport const CLEAR_WIDGET_MODAL = 'app/WidgetModal/CLEAR_WIDGET_MODAL'\nexport const SET_WIDGET_ONCLICK_SEARCH =\n 'app/WidgetModal/SET_WIDGET_ONCLICK_SEARCH'\nexport const SET_WIDGET_SIZE = 'app/WidgetModal/SET_WIDGET_SIZE'\nexport const SET_WIDGET_OPTIONS = 'app/WidgetModal/SET_WIDGET_OPTIONS'\nexport const GET_SONRAI_SAVED_SEARCHES =\n 'app/WidgetModal/GET_SONRAI_SAVED_SEARCHES'\nexport const SET_SONRAI_SAVED_SEARCHES =\n 'app/WidgetModal/SET_SONRAI_SAVED_SEARCHES'\nexport const SET_WIDGET_SONRAI_SAVED_SEARCH =\n 'app/WidgetModal/SET_WIDGET_SONRAI_SAVED_SEARCH'\nexport const LOAD_WIDGET = 'app/WidgetModal/LOAD_WIDGET'\nexport const WIDGET_SONRAI_MASTER_QUERY = 'WIDGET_SONRAI_MASTER_QUERY'\nexport const TOGGLE_PREVIEW_WIDGET = 'app/WidgetModal/TOGGLE_PREVIEW_WIDGET'\n","/*\n * WidgetModal Messages\n *\n * This contains all the text for the WidgetModal component.\n */\nimport { defineMessages } from 'react-intl'\n\nexport default defineMessages({\n header: {\n id: 'app.containers.WidgetModal.header',\n defaultMessage: 'Add Widget',\n },\n updateHeader: {\n id: 'app.containers.WidgetModal.updateHeader',\n defaultMessage: 'Edit Widget',\n },\n widgetPreview: {\n id: 'app.containers.WidgetModal.widgetPreview',\n defaultMessage: 'Widget Preview',\n },\n})\n","/*\n *\n * WidgetModal actions\n *\n */\n\nimport {\n LOAD_WIDGET,\n SET_WIDGET_TITLE,\n SET_WIDGET_SUB_TITLE,\n SET_WIDGET_TYPE,\n SET_WIDGET_SELECTOR,\n SET_WIDGET_FORMATTER,\n SET_WIDGET_SEARCH_FIELD,\n SET_WIDGET_SAVED_SEARCH,\n CLEAR_WIDGET_MODAL,\n SET_WIDGET_SIZE,\n SET_WIDGET_OPTIONS,\n SET_SONRAI_SAVED_SEARCHES,\n SET_WIDGET_SONRAI_SAVED_SEARCH,\n GET_SONRAI_SAVED_SEARCHES,\n TOGGLE_PREVIEW_WIDGET,\n} from './constants'\nimport { createAction } from 'redux-actions'\n\nexport const clearWidgetModal = createAction(CLEAR_WIDGET_MODAL)\nexport const getSavedSonraiSearches = createAction(GET_SONRAI_SAVED_SEARCHES)\nexport const setSonraiSavedSearches = createAction(\n SET_SONRAI_SAVED_SEARCHES,\n searches => ({\n searches,\n })\n)\n\nexport const setWidgetSavedSearch = createAction(\n SET_WIDGET_SAVED_SEARCH,\n (search, index) => {\n return {\n search,\n index,\n }\n }\n)\n\nexport const setWidgetSonraiSearch = createAction(\n SET_WIDGET_SONRAI_SAVED_SEARCH,\n (search, index) => {\n return {\n search,\n index,\n }\n }\n)\n\nexport const setWidgetSearchField = createAction(\n SET_WIDGET_SEARCH_FIELD,\n (field, index, savedSearches) => ({\n field,\n index,\n savedSearches,\n })\n)\nexport const setWidgetTitle = createAction(SET_WIDGET_TITLE, input => ({\n input,\n}))\nexport const setWidgetSubTitle = createAction(SET_WIDGET_SUB_TITLE, input => ({\n input,\n}))\nexport const setWidgetType = createAction(SET_WIDGET_TYPE, input => ({\n input,\n}))\nexport const setWidgetSelector = createAction(SET_WIDGET_SELECTOR, input => ({\n input,\n}))\nexport const setWidgetFormatter = createAction(SET_WIDGET_FORMATTER, input => ({\n input,\n}))\n\nexport const setWidgetSize = createAction(SET_WIDGET_SIZE)\nexport const setWidgetOptions = createAction(SET_WIDGET_OPTIONS)\nexport const loadWidget = createAction(LOAD_WIDGET)\nexport const togglePreviewWidget = createAction(TOGGLE_PREVIEW_WIDGET)\n","import { all, put, takeLatest } from 'redux-saga/effects'\n\nimport { getClient } from 'apolloClient'\nimport gql from 'graphql-tag'\n\nfunction* mySaga() {\n // yield all(([])\n}\n\nexport default mySaga\n","/**\n *\n * RatioWidget\n *\n */\n\nimport React from 'react'\nimport PropTypes from 'prop-types'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport { Query } from 'react-apollo'\nimport _ from 'lodash'\nimport gql from 'graphql-tag'\nimport TextLink from 'components/TextLink'\nimport Card, { BottomTitle, CardBody } from 'components/Card'\nimport WidgetCard from 'components/WidgetCard'\nimport { queryHasPivotFilter } from 'query-builder'\nimport { getSearchIdForSonraiSearches } from 'utils/sonraiUtils'\nimport { Map } from 'immutable'\nimport {\n getFields,\n getSearchCard,\n getSelection,\n hasSonraiSearch,\n combineQueries,\n getBareSearchQueryString,\n} from 'query-builder'\n\nconst styles = {\n numerator: {\n fontWeight: '300',\n },\n denominator: {\n color: '#555',\n },\n number: {\n textAlign: 'center',\n },\n percentNumber: {\n fontWeight: '300',\n },\n averageNumber: {\n fontWeight: '300',\n },\n percentSign: {\n fontSize: '80%',\n color: '#555',\n },\n searchLink: {\n fontWeight: '300',\n cursor: 'pointer',\n // marginTop: '-0.5em',\n },\n}\n\nclass RatioWidget extends React.Component {\n getQuery = () => {\n const numQueryConfig = this.getQueryConfig('num')\n const denomQueryConfig = this.getQueryConfig('denom')\n\n return combineQueries('ratioQuery', numQueryConfig, denomQueryConfig)\n }\n\n getQueryConfig = key => {\n if (hasSonraiSearch(this.props.options, key)) {\n return {\n gqlStatement: `\n ${key}: ${getBareSearchQueryString(\n this.props.options.sonraiSearches[key]\n )}`,\n }\n } else {\n return this.getSavedQuery(key)\n }\n }\n\n getSavedQuery = searchIndex => {\n const { getQueryBuilder, savedSearches, resultLayout } = this.props\n\n const fields = getFields(\n savedSearches,\n resultLayout.indexedSearches[searchIndex]\n )\n if (fields.isEmpty()) {\n return {}\n }\n\n const queryBuilder = getQueryBuilder(fields)\n queryBuilder.countsOnly()\n\n const rootField = queryBuilder.fields.find(\n statement => !statement.get('parentId'),\n null,\n Map()\n )\n\n const variables = queryBuilder.getVariables()\n const queryString = queryBuilder.buildPivotableSourceForField(\n rootField.toJS(),\n undefined,\n undefined,\n variables\n )\n\n return {\n gqlStatement: `${searchIndex}: ${queryString}`,\n variables,\n }\n }\n\n getSelectionPath = (searchIndex, data) => {\n if (hasSonraiSearch(this.props.options, searchIndex)) {\n return this.selectFromSonraiSearchData(searchIndex, data)\n } else {\n return this.selectFromSavedSearchData(searchIndex)\n }\n }\n\n selectFromSonraiSearchData = (searchIndex, data) => {\n if (_.isEmpty(data)) {\n return []\n } else {\n let key = Object.keys(data[searchIndex].Query)[0]\n let str = `${searchIndex}.Query.${key}.count`\n\n return str\n }\n }\n\n selectFromSavedSearchData = searchIndex => {\n const fields = getFields(\n this.props.savedSearches,\n this.props.resultLayout.indexedSearches[searchIndex]\n ).toJS()\n\n const searchCard = getSearchCard(this.props.resultLayout, searchIndex)\n\n const path = getSelection(fields, searchCard, 'count')\n path[0] = searchIndex //Root select type becomes aliased to the searchIndex\n return path\n }\n\n getFontSizes = (numerator, denominator) => {\n const width = this.props.layout.w\n const numNumChars = numerator.toString().length\n const numDenomChars = denominator.toString().length + 1\n\n const numDigitsAdjusted = numNumChars + numDenomChars\n let numSize = 70\n let denomSize = 50\n\n let maxDigitsPerWidth = 0.5\n\n while (numDigitsAdjusted > maxDigitsPerWidth * width && numSize > 15) {\n maxDigitsPerWidth = maxDigitsPerWidth + 0.5\n numSize = numSize - 13\n denomSize = denomSize - 9\n }\n\n return {\n num: { fontSize: `${numSize > 12 ? numSize : 12}px` },\n denom: { fontSize: `${denomSize > 12 ? denomSize : 12}px` },\n }\n }\n\n getTitleFontSize = title => {\n const width = this.props.layout.w\n const numChars = title.length\n\n let fontSize = 16\n\n let textToWidthRatio = numChars / width\n\n if (textToWidthRatio > 5) {\n fontSize = 13\n }\n\n return {\n fontSize: `${fontSize}px`,\n }\n }\n\n hasOnclick = () => {\n return (\n !!this.props.resultLayout.indexedSearches.onclick ||\n !!this.props.options.sonraiSearches.sonraionclick\n )\n }\n\n getNumSearchName = () => {\n return hasSonraiSearch(this.props.options, 'num')\n ? this.props.options.sonraiSearches.num\n : this.props.savedSearches.getIn([\n this.props.resultLayout.indexedSearches.num,\n 'name',\n ])\n }\n\n getDenomSearchName = () => {\n return hasSonraiSearch(this.props.options, 'denom')\n ? this.props.options.sonraiSearches.denom\n : this.props.savedSearches.getIn([\n this.props.resultLayout.indexedSearches.denom,\n 'name',\n ])\n }\n\n getTitleSearchName = () => {\n if (this.hasOnclick()) {\n return hasSonraiSearch(this.props.options, 'sonraionclick')\n ? this.props.options.sonraiSearches.sonraionclick\n : this.props.savedSearches.getIn([\n this.props.resultLayout.indexedSearches.onclick,\n 'name',\n ])\n }\n\n return hasSonraiSearch(this.props.options, 'num')\n ? this.props.options.sonraiSearches.num\n : this.props.savedSearches.getIn([\n this.props.resultLayout.indexedSearches.num,\n 'name',\n ])\n }\n\n onClickSearch = type => {\n if (type) {\n this.props.onClickSearch({\n savedSearchId: this.props.resultLayout.indexedSearches[type],\n sonraiSearchName: this.props.options.sonraiSearches[type],\n searchTitle: this.props.title,\n })\n } else if (this.hasOnclick()) {\n this.props.onClickSearch({\n savedSearchId: this.props.resultLayout.indexedSearches.onclick,\n sonraiSearchName: this.props.options.sonraiSearches.sonraionclick,\n searchTitle: this.props.title,\n })\n } else {\n this.props.onClickSearch({\n savedSearchId: this.props.resultLayout.indexedSearches.num,\n sonraiSearchName: this.props.options.sonraiSearches.num,\n searchTitle: this.props.title,\n })\n }\n }\n\n getRatioNumber = (numerator, denominator) => {\n const fontSizes = this.getFontSizes(numerator, denominator)\n return (\n \n this.onClickSearch('num')}\n >\n \n {numerator}\n \n \n /\n this.onClickSearch('denom')}\n >\n \n {denominator}\n \n \n
\n )\n }\n\n getFontSize = display => {\n const width = this.props.layout.w\n const numChars = display.length\n\n let maxDigitsPerWidth = 1\n let fontSize = 50\n\n while (numChars > maxDigitsPerWidth * width && fontSize > 20) {\n maxDigitsPerWidth++\n fontSize = fontSize - 4\n }\n\n return {\n fontSize: `${fontSize}px`,\n }\n }\n\n getPercentNumber = (numerator, denominator) => {\n let percent = ((numerator / denominator) * 100).toFixed(2)\n const fontSize = this.getFontSize(`${percent}%`)\n if (percent === 'NaN') {\n percent = 0\n }\n if (denominator < 1) {\n percent = 'N/A'\n }\n return (\n this.onClickSearch()}\n >\n \n {percent}\n \n \n {percent !== 0 && percent !== 'N/A' && '%'}\n \n
\n )\n }\n\n getAverageNumber = (numerator, denominator) => {\n let avg = (numerator / denominator).toFixed(2)\n const fontSize = this.getFontSize(`${avg}`)\n if (avg === 'NaN') {\n avg = 0\n }\n return (\n this.onClickSearch()}\n >\n {avg}\n
\n )\n }\n\n getNumber = data => {\n if (_.isEmpty(data)) {\n return ''\n }\n\n const denomSelection = this.getSelectionPath('denom', data)\n const numSelection = this.getSelectionPath('num', data)\n\n const denominator = _.get(data, denomSelection) || 0\n const numerator = _.get(data, numSelection) || 0\n\n if (\n this.props.resultLayout.widgetOptions &&\n this.props.resultLayout.widgetOptions.displayType === 'percentage'\n ) {\n return this.getPercentNumber(numerator, denominator)\n } else if (\n this.props.resultLayout.widgetOptions &&\n this.props.resultLayout.widgetOptions.displayType === 'average'\n ) {\n return this.getAverageNumber(numerator, denominator)\n } else {\n return this.getRatioNumber(numerator, denominator)\n }\n }\n\n getSearchId = () => {\n const { options, sonraiSearches, resultLayout } = this.props\n let searchObj = Map({ uiSearch: null, advancedSearch: null })\n if (hasSonraiSearch(options, 'sonraionclick')) {\n const searches = getSearchIdForSonraiSearches(options, sonraiSearches)\n searchObj = searchObj.set('advancedSearch', searches)\n } else {\n searchObj = searchObj.set(\n 'uiSearch',\n resultLayout.indexedSearches.onclick\n )\n }\n return searchObj\n }\n\n render() {\n if (this.props.data === undefined) {\n const searchId = this.getSearchId()\n const queryConfig = this.getQuery()\n\n if (!queryConfig.gqlStatement) {\n return (\n \n )\n }\n\n const filtered = queryHasPivotFilter(queryConfig.gqlStatement)\n return (\n \n {({ error, data, refetch, networkStatus }) => {\n return (\n this.onClickSearch()}>\n {this.props.title}\n \n }\n filtered={filtered}\n description={_.get(this.props.resultLayout, [\n 'widgetOptions',\n 'description',\n ])}\n >\n \n \n {this.getNumber(data)}\n
\n \n \n this.onClickSearch()}\n >\n {this.props.title}\n \n \n \n )\n }}\n \n )\n } else {\n return (\n \n \n \n {this.getNumber(this.props.data)}\n
\n \n \n {this.props.title}\n \n \n )\n }\n }\n}\n\nRatioWidget.defaultProps = {\n layout: {\n w: 10,\n },\n}\n\nRatioWidget.propTypes = {\n allowDelete: PropTypes.bool,\n allowUpdate: PropTypes.bool,\n disableToolbar: PropTypes.bool,\n data: PropTypes.object,\n getQueryBuilder: PropTypes.func.isRequired,\n layout: PropTypes.shape({\n w: PropTypes.number,\n }),\n onRemove: PropTypes.func.isRequired,\n resultLayout: PropTypes.object.isRequired,\n savedSearches: ImmutablePropTypes.map.isRequired,\n options: PropTypes.shape({\n sonraiSearches: PropTypes.objectOf(PropTypes.string),\n }),\n static: PropTypes.bool.isRequired,\n title: PropTypes.string,\n toggleStatic: PropTypes.func.isRequired,\n onEdit: PropTypes.func,\n onClickSearch: PropTypes.func,\n widget: PropTypes.object,\n sonraiSearches: ImmutablePropTypes.iterable,\n}\n\nexport default RatioWidget\n","import React from 'react'\nimport PropTypes from 'prop-types'\n\nconst styles = {\n previewContainer: {\n display: 'flex',\n flexDirection: 'column',\n justifyContent: 'center',\n alignItems: 'center',\n backgroundColor: '#fafafa',\n borderRadius: '5px',\n padding: '2em',\n },\n previewTitle: {\n marginBottom: '1em',\n color: '#aaa',\n },\n}\n\nexport const PreviewContainer = ({ children }) => {\n return (\n \n
Preview
\n {children}\n
\n )\n}\n\nPreviewContainer.propTypes = {\n children: PropTypes.node,\n}\n\nexport default PreviewContainer\n","import React from 'react'\nimport PropTypes from 'prop-types'\nimport { Input } from 'reactstrap'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport { List } from 'immutable'\nimport _ from 'lodash'\nimport FormLabel from 'components/FormLabel'\nimport CombinedSearches from 'components/CombinedSearches'\nimport RatioWidget from 'components/RatioWidget'\nimport PreviewContainer from './PreviewContainer'\n\nconst styles = {\n wrapperWithPreview: {\n display: 'grid',\n gridTemplateColumns: '35% 1fr',\n gridColumnGap: '2em',\n },\n}\nexport class RatioConfig extends React.Component {\n componentDidUpdate() {\n this.updateValidity()\n }\n\n updateValidity = () => {\n const hasNumSonraiSearch = !!this.props.widgetSonraiSearches.num\n const hasNumSavedSearch =\n !!this.props.widgetSavedSearches.num && !!this.props.widgetSearchCards.num\n\n const hasDenomSonraiSearch = !!this.props.widgetSonraiSearches.denom\n const hasDenomSavedSearch =\n !!this.props.widgetSavedSearches.denom &&\n !!this.props.widgetSearchCards.denom\n\n const hasValidNumSearch = hasNumSavedSearch || hasNumSonraiSearch\n const hasValidDenomSearch = hasDenomSavedSearch || hasDenomSonraiSearch\n\n const hasTitle = this.props.widgetTitle\n\n const valid = hasValidNumSearch && hasValidDenomSearch && hasTitle\n this.props.setValidity(valid)\n }\n\n setTitle = e => {\n this.props.setWidgetTitle(e.target.value)\n }\n\n setDescription = e => {\n const string = e.target.value\n\n this.props.setWidgetOptions({\n description: string,\n })\n }\n\n setNumeratorSearch = option => {\n this.props.setWidgetSavedSearch(option ? option.value : null, 'num')\n }\n\n setDenominatorSearch = option => {\n this.props.setWidgetSavedSearch(option ? option.value : null, 'denom')\n }\n\n setOnclickSearch = option => {\n this.props.setWidgetSavedSearch(option ? option.value : null, 'onclick')\n }\n\n setSonraiOnclickSearch = selectedOption => {\n this.props.setWidgetSonraiSearch(\n selectedOption ? selectedOption.value : null,\n 'sonraionclick'\n )\n }\n\n setNumeratorField = option => {\n this.props.setWidgetSearchField(option ? option.value : null, 'num')\n }\n\n setDenominatorField = option => {\n this.props.setWidgetSearchField(option ? option.value : null, 'denom')\n }\n\n updateDisplayType = e => {\n this.props.setWidgetOptions({\n displayType: e.target.value,\n })\n }\n\n setSonraiSearchDenom = selectedOption => {\n this.props.setWidgetSonraiSearch(\n selectedOption ? selectedOption.value : null,\n 'denom'\n )\n }\n\n setSonraiSearchNum = selectedOption => {\n this.props.setWidgetSonraiSearch(\n selectedOption ? selectedOption.value : null,\n 'num'\n )\n }\n\n render() {\n const denominatorSearchId = this.props.widgetSavedSearches['denom']\n const denominatorField = this.props.widgetSearchCards['denom']\n\n const numeratorSearchId = this.props.widgetSavedSearches['num']\n const numeratorField = this.props.widgetSearchCards['num']\n\n const onclickSearchId = this.props.widgetSavedSearches['onclick']\n const sonraiOnclickSearch = this.props.widgetSonraiSearches['sonraionclick']\n\n const numeratorSonraiSearchName = this.props.widgetSonraiSearches['num']\n const denomSonraiSearchName = this.props.widgetSonraiSearches['denom']\n\n const denomSearchFieldsList = this.props.searchCards.get(\n denominatorSearchId,\n List()\n )\n const numSearchFieldsList = this.props.searchCards.get(\n numeratorSearchId,\n List()\n )\n return (\n \n )\n }\n}\n\nRatioConfig.propTypes = {\n getQueryBuilder: PropTypes.func.isRequired,\n previewWidget: PropTypes.bool,\n savedSearches: ImmutablePropTypes.mapOf(\n ImmutablePropTypes.contains({\n sid: PropTypes.string,\n name: PropTypes.string,\n })\n ),\n searchCards: ImmutablePropTypes.mapOf(\n ImmutablePropTypes.contains({\n id: PropTypes.string,\n name: PropTypes.string,\n })\n ),\n setWidgetOptions: PropTypes.func.isRequired,\n setWidgetSavedSearch: PropTypes.func,\n setWidgetSearchField: PropTypes.func,\n setWidgetTitle: PropTypes.func,\n setValidity: PropTypes.func,\n widgetSavedSearches: PropTypes.objectOf(PropTypes.array),\n widgetSearchCards: PropTypes.objectOf(PropTypes.object),\n widgetSonraiSearches: PropTypes.objectOf(PropTypes.string),\n widgetTitle: PropTypes.string,\n widgetOptions: PropTypes.shape({\n description: PropTypes.string,\n displayType: PropTypes.string,\n }),\n savedSonraiSearches: ImmutablePropTypes.iterableOf(\n ImmutablePropTypes.contains({\n sid: PropTypes.string,\n name: PropTypes.string,\n })\n ),\n setWidgetSonraiSearch: PropTypes.func,\n}\n\nexport default RatioConfig\n","import { defineMessages } from 'react-intl'\n\nexport default defineMessages({\n noColor: {\n id: 'app.components.ColorPicker.noColor',\n defaultMessage: 'Random Color',\n },\n})\n","import React from 'react'\nimport PropTypes from 'prop-types'\nimport { TwitterPicker } from 'react-color'\nimport uuid from 'uuid/v4'\nimport { FormattedMessage } from 'react-intl'\n\nimport Popover, { PopoverAnchor, PopoverBody } from 'components/Popover'\nimport themeable, { themeShape } from 'containers/ThemeManager/Themeable'\nimport messages from './messages'\n\nconst styles = {\n colorPreview: {\n width: '1em',\n height: '1em',\n border: '1px solid #888',\n display: 'inline-block',\n marginRight: '0.5em',\n borderRadius: '0',\n },\n colorContainer: {\n display: 'flex',\n alignItems: 'center',\n },\n noColor: {\n fontStyle: 'italic',\n color: '#777',\n },\n}\n\nexport class ColorPicker extends React.Component {\n constructor(props) {\n super(props)\n\n this.state = {\n showColorPicker: false,\n }\n\n this.uuid = uuid()\n }\n\n onPickColor = color => {\n this.props.onPickColor(color.hex)\n this.setState({\n showColorPicker: false,\n })\n }\n\n toggleColorPicker = () => {\n this.setState(state => ({\n showColorPicker: !state.showColorPicker,\n }))\n }\n\n onPopoverToggle = newVisibility => {\n this.setState({\n showColorPicker: newVisibility,\n })\n }\n\n render() {\n return (\n \n
\n \n \n \n\n {this.props.color ? (\n this.props.color\n ) : (\n \n {' '}\n {this.props.noColorMessage || (\n \n )}\n \n )}\n
\n \n \n \n \n \n
\n )\n }\n}\n\nColorPicker.propTypes = {\n onPickColor: PropTypes.func,\n color: PropTypes.string,\n noColorMessage: PropTypes.string,\n theme: themeShape,\n}\n\nexport default themeable(ColorPicker)\n","import React, { Fragment } from 'react'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport PropTypes from 'prop-types'\nimport { Table, Input, Label } from 'reactstrap'\nimport { List } from 'immutable'\nimport _ from 'lodash'\n\nimport IHelp from 'containers/IHelp'\nimport BigStatWidget from 'components/BigStatWidget'\nimport TextLink from 'components/TextLink'\nimport Button from 'components/Button'\nimport ColorPicker from 'components/ColorPicker'\nimport BorderlessButton from 'components/BorderlessButton'\nimport Icon from 'components/Icon'\nimport FormLabel from 'components/FormLabel'\nimport CombinedSearches from 'components/CombinedSearches'\nimport { BOUNDARY_OPTIONS } from 'appConstants'\nimport PreviewContainer from './PreviewContainer'\n\nconst styles = {\n sectionTitle: {\n fontSize: '1em',\n fontWeight: '400',\n display: 'grid',\n gridTemplateColumns: '1fr auto',\n },\n addThresholdButton: {\n display: 'flex',\n alignItems: 'center',\n },\n addThresholdIcon: {\n marginRight: '0.5em',\n fontSize: '20px',\n },\n colorPreview: {\n width: '1em',\n height: '1em',\n border: '1px solid #888',\n display: 'inline-block',\n marginRight: '0.5em',\n borderRadius: '0',\n },\n cancelAddThreshold: {\n marginLeft: '0.5em',\n },\n colorContainer: {\n display: 'flex',\n alignItems: 'center',\n },\n newThresholdWrapper: {\n display: 'grid',\n gridTemplateColumns: '1fr 1fr',\n },\n newcolorWrapper: {\n margin: '1em 0',\n },\n thresholdButton: {\n marginBottom: '15px',\n marginRight: '10px',\n width: '45%',\n outline: 'none',\n },\n bigCountPreview: {\n minWidth: '225px',\n height: '225px',\n },\n wrapperWithPreview: {\n display: 'grid',\n gridTemplateColumns: '35% 1fr',\n gridColumnGap: '2em',\n },\n config: {\n display: 'grid',\n gridTemplateColumns: '1fr 1fr',\n gridColumnGap: '2em',\n },\n}\n\nexport class BigCountConfig extends React.Component {\n state = {\n showAddThreshold: false,\n startOfThreshold: 0,\n endOfThreshold: 0,\n showColorPicker: false,\n newThresholdColor: '#FFFFFF',\n operatorSign: '',\n operatorVal: 0,\n range: true,\n boundary: false,\n }\n\n componentDidUpdate() {\n this.updateValidity()\n }\n\n updateValidity = () => {\n const hasSonraiSearch = !!this.props.widgetSonraiSearches.bigcountsearch\n const hasSavedSearch =\n !!this.props.widgetSavedSearches.bigcountsearch &&\n !!this.props.widgetSearchCards.bigcountsearch\n const hasTitle = this.props.widgetTitle\n\n const valid = (hasSonraiSearch || hasSavedSearch) && hasTitle\n this.props.setValidity(valid)\n }\n\n setTitle = e => {\n this.props.setWidgetTitle(e.target.value)\n }\n\n selectSavedSearch = selectedOption => {\n this.props.setWidgetSavedSearch(\n selectedOption ? selectedOption.value : null,\n 'bigcountsearch'\n )\n }\n\n selectSearchField = selectedOption => {\n this.props.setWidgetSearchField(\n selectedOption ? selectedOption.value : null,\n 'bigcountsearch'\n )\n }\n\n setSonraiSearch = selectedOption => {\n this.props.setWidgetSonraiSearch(\n selectedOption ? selectedOption.value : null,\n 'bigcountsearch'\n )\n }\n\n setDescription = e => {\n const string = e.target.value\n\n this.props.setWidgetOptions({\n description: string,\n })\n }\n\n removeMarker = index => {\n const thresholds = [...this.props.widgetOptions.thresholds]\n\n thresholds.splice(index, 1)\n this.props.setWidgetOptions({\n thresholds,\n })\n }\n\n handleStartOfThreshold = e => {\n this.setState({\n startOfThreshold: e.target.value,\n })\n }\n\n showColorPicker = () => {\n this.setState({\n showColorPicker: true,\n })\n }\n\n onPickColor = color => {\n this.setState({\n newThresholdColor: color,\n })\n }\n\n toggleColorPicker = () => {\n this.setState(state => ({\n showColorPicker: !state.showColorPicker,\n }))\n }\n\n handleEndOfThreshold = e => {\n this.setState({\n endOfThreshold: e.target.value,\n })\n }\n\n toggleAddThreshold = () => {\n this.setState(state => ({\n showAddThreshold: !state.showAddThreshold,\n }))\n }\n\n handleSelect = event => {\n this.setState({\n operatorSign: event.target.value,\n })\n }\n\n handleOpperator = event => {\n this.setState({\n operatorVal: event.target.value,\n })\n }\n\n handleBoundaryButton = () => {\n this.setState({\n boundary: true,\n range: false,\n newThresholdColor: '#FFFFFF',\n startOfThreshold: 0,\n endOfThreshold: 0,\n operatorSign: BOUNDARY_OPTIONS.GREATER,\n operatorVal: 0,\n })\n }\n\n handleRangeButton = () => {\n this.setState({\n range: true,\n boundary: false,\n newThresholdColor: '#FFFFFF',\n startOfThreshold: 0,\n endOfThreshold: 0,\n operatorSign: '',\n operatorVal: 0,\n })\n }\n\n resetAddThreshold = () => {\n this.setState({\n showAddThreshold: false,\n showColorPicker: false,\n newThresholdColor: '#FFFFFF',\n startOfThreshold: 0,\n endOfThreshold: 0,\n operatorSign: '',\n operatorVal: 0,\n })\n }\n\n addThreshold = type => {\n const options = this.props.widgetOptions || {}\n const thresholds = options.thresholds || []\n\n if (type === 'between') {\n thresholds.push({\n start: this.state.startOfThreshold,\n end: this.state.endOfThreshold,\n color: this.state.newThresholdColor,\n type: type,\n })\n } else {\n thresholds.push({\n operatorSign: this.state.operatorSign,\n operatorVal: this.state.operatorVal,\n color: this.state.newThresholdColor,\n type: type,\n })\n }\n\n this.props.setWidgetOptions({\n thresholds,\n })\n\n this.resetAddThreshold()\n }\n\n renderThresholdsConfig = () => {\n const thresholds = this.props.widgetOptions.thresholds || []\n return (\n \n \n {thresholds.map((threshold, index) => (\n \n {threshold.type === 'between' ? (\n \n \n this.removeMarker(index)}\n >\n \n \n | \n {threshold.start} | \n To | \n {threshold.end} | \n \n \n \n {threshold.color}\n \n | \n \n ) : (\n \n \n this.removeMarker(index)}\n >\n \n \n | \n {threshold.operatorSign} | \n {threshold.operatorVal} | \n \n \n \n {threshold.color}\n \n | \n \n )}\n
\n ))}\n \n
\n )\n }\n\n renderNewThresholdConfig = () => {\n return (\n \n
\n \n \n
\n {this.state.range ? (\n
\n ) : (\n
\n )}\n
\n \n \n
\n\n
\n \n \n Cancel\n \n
\n
\n )\n }\n\n renderPreview = () => {\n return (\n \n \n Widget Title\n )\n }\n />\n
\n \n )\n }\n\n render() {\n const sonraiSearchName = this.props.widgetSonraiSearches['bigcountsearch']\n const savedSearchId = this.props.widgetSavedSearches['bigcountsearch']\n const savedSearchField = this.props.widgetSearchCards['bigcountsearch']\n const searchFieldsList = this.props.searchCards.get(\n this.props.widgetSavedSearches['bigcountsearch'],\n List()\n )\n\n return (\n \n {this.props.previewWidget && this.renderPreview()}\n\n
\n
\n )\n }\n}\n\nBigCountConfig.propTypes = {\n getQueryBuilder: PropTypes.func,\n previewWidget: PropTypes.bool,\n savedSearches: ImmutablePropTypes.iterableOf(\n ImmutablePropTypes.contains({\n sid: PropTypes.string,\n name: PropTypes.string,\n })\n ),\n searchCards: ImmutablePropTypes.mapOf(\n PropTypes.oneOfType([\n ImmutablePropTypes.contains({\n id: PropTypes.string,\n name: PropTypes.string,\n }),\n PropTypes.array,\n ])\n ),\n setWidgetOptions: PropTypes.func,\n setWidgetSavedSearch: PropTypes.func,\n setWidgetSearchField: PropTypes.func,\n setWidgetTitle: PropTypes.func,\n setValidity: PropTypes.func,\n widgetSavedSearches: PropTypes.object,\n widgetSearchCards: PropTypes.object,\n widgetSonraiSearches: PropTypes.object,\n widgetTitle: PropTypes.string,\n widgetOptions: PropTypes.shape({\n thresholds: PropTypes.arrayOf(\n PropTypes.shape({\n start: PropTypes.number,\n end: PropTypes.number,\n color: PropTypes.string,\n })\n ),\n description: PropTypes.string,\n }).isRequired,\n savedSonraiSearches: ImmutablePropTypes.iterableOf(\n ImmutablePropTypes.contains({\n sid: PropTypes.string,\n name: PropTypes.string,\n })\n ),\n setWidgetSonraiSearch: PropTypes.func,\n}\n\nexport default BigCountConfig\n","/**\n *\n * GaugeWidget\n *\n */\n\nimport React from 'react'\nimport PropTypes from 'prop-types'\nimport { Gauge } from './gauge.min'\nimport _ from 'lodash'\nimport themeable, { themeShape } from 'containers/ThemeManager/Themeable'\n\nclass GaugeComponent extends React.Component {\n constructor(props) {\n super(props)\n this.canvasRef = React.createRef()\n }\n\n waitForRef = () => {\n return new Promise(resolve => {\n const wait = () => {\n if (!this.canvasRef.current) {\n setTimeout(wait, 200)\n return\n }\n\n return resolve()\n }\n\n wait()\n })\n }\n\n componentDidMount() {\n this.waitForRef().then(this.drawGauge)\n }\n\n shouldComponentUpdate(newProps) {\n return (\n !_.isEqual(newProps.markers, this.props.markers) ||\n newProps.value !== this.props.value\n )\n }\n\n componentDidUpdate() {\n if (this.gauge) {\n this.setOptions(this.gauge)\n this.gauge.set(this.props.value) // set actual value\n this.gauge.render()\n }\n }\n\n setOptions = gauge => {\n const opts = {\n angle: 0, // The span of the gauge arc\n lineWidth: 0.3, // The line thickness\n radiusScale: 0.8,\n pointer: {\n length: 0.5, // // Relative to gauge radius\n strokeWidth: 0.035, // The thickness\n color: '#333', // Fill color\n hoveredColor: '#000000',\n valueColor: this.props.theme.primary,\n valueCircleColor: this.props.theme.secondary,\n },\n limitMax: true, // If false, max value increases automatically if value > maxValue\n limitMin: true, // If true, the min value of the gauge will be fixed\n highDpiSupport: true, // High resolution support\n staticZones: this.props.markers.map(marker => {\n return {\n strokeStyle: marker.color,\n min: marker.start,\n max: marker.end,\n }\n }),\n tipLabel: true,\n }\n\n gauge.setOptions(opts)\n }\n\n drawGauge = () => {\n if (!this.gauge && this.canvasRef.current) {\n const ref = this.canvasRef.current\n\n var gauge = new Gauge(ref)\n this.setOptions(gauge)\n\n gauge.maxValue = 100\n gauge.setMinValue(0)\n gauge.animationSpeed = 32\n gauge.set(this.props.value)\n gauge.render()\n\n this.gauge = gauge\n }\n }\n\n render() {\n return (\n \n )\n }\n}\n\nGaugeComponent.propTypes = {\n markers: PropTypes.arrayOf(\n PropTypes.shape({\n start: PropTypes.number,\n end: PropTypes.number,\n color: PropTypes.string,\n })\n ),\n onClick: PropTypes.func,\n value: PropTypes.number,\n theme: themeShape,\n}\n\nexport default themeable(GaugeComponent)\n","/**\n *\n * GaugeWidget\n *\n */\n\nimport React from 'react'\nimport PropTypes from 'prop-types'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport { Query } from 'react-apollo'\nimport _ from 'lodash'\nimport gql from 'graphql-tag'\nimport Gauge from './Gauge'\nimport uuid from 'uuid/v4'\n\nimport TextLink from 'components/TextLink'\nimport Card, { BottomTitle, CardBody } from 'components/Card'\nimport WidgetCard from 'components/WidgetCard'\nimport { queryHasPivotFilter } from 'query-builder'\nimport { getSearchIdForSonraiSearches } from 'utils/sonraiUtils'\nimport { Map } from 'immutable'\nimport {\n getFields,\n getSearchCard,\n getSelection,\n hasSonraiSearch,\n combineQueries,\n getBareSearchQueryString,\n} from 'query-builder'\n\nclass GaugeWidget extends React.Component {\n constructor(props) {\n super(props)\n this.uuid = uuid()\n }\n\n getQuery = () => {\n const numQueryConfig = this.getQueryConfig('num')\n const denomQueryConfig = this.getQueryConfig('denom')\n\n return combineQueries('gaugeQuery', numQueryConfig, denomQueryConfig)\n }\n\n getQueryConfig = key => {\n if (hasSonraiSearch(this.props.options, key)) {\n return {\n gqlStatement: `\n ${key}: ${getBareSearchQueryString(\n this.props.options.sonraiSearches[key]\n )}\n `,\n }\n } else {\n return this.getSavedQuery(key)\n }\n }\n\n getSavedQuery = searchIndex => {\n const { getQueryBuilder, savedSearches, resultLayout } = this.props\n\n const fields = getFields(\n savedSearches,\n resultLayout.indexedSearches[searchIndex]\n )\n if (fields.isEmpty()) {\n return {}\n }\n\n const queryBuilder = getQueryBuilder(fields)\n queryBuilder.countsOnly()\n\n const rootField = queryBuilder.fields.find(\n statement => !statement.get('parentId'),\n null,\n Map()\n )\n\n const variables = queryBuilder.getVariables()\n const queryString = queryBuilder.buildPivotableSourceForField(\n rootField.toJS(),\n undefined,\n undefined,\n variables\n )\n\n return {\n gqlStatement: `${searchIndex}: ${queryString}`,\n variables,\n }\n }\n\n hasOnclick = () => {\n return (\n !!this.props.resultLayout.indexedSearches.onclick ||\n !!this.props.options.sonraiSearches.sonraionclick\n )\n }\n\n onClickSearch = () => {\n if (this.hasOnclick()) {\n this.props.onClickSearch({\n savedSearchId: this.props.resultLayout.indexedSearches.onclick,\n sonraiSearchName: this.props.options.sonraiSearches.sonraionclick,\n searchTitle: this.props.title,\n })\n }\n }\n\n getTitleSearchName = () => {\n if (this.hasOnclick()) {\n return hasSonraiSearch(this.props.options, 'sonraionclick')\n ? this.props.options.sonraiSearches.sonraionclick\n : this.props.savedSearches.getIn([\n this.props.resultLayout.indexedSearches.onclick,\n 'name',\n ])\n }\n }\n\n getSelection = (searchIndex, data) => {\n if (hasSonraiSearch(this.props.options, searchIndex)) {\n return this.selectFromSonraiSearchData(searchIndex, data)\n } else {\n return this.selectFromSavedSearchData(searchIndex)\n }\n }\n\n selectFromSonraiSearchData = (searchIndex, data) => {\n if (\n _.isEmpty(data) ||\n _.isEmpty(data[searchIndex]) ||\n _.isEmpty(data[searchIndex].Query)\n ) {\n return []\n } else {\n let key = Object.keys(data[searchIndex].Query)[0]\n let str = `${searchIndex}.Query.${key}.count`\n\n return str\n }\n }\n\n selectFromSavedSearchData = searchIndex => {\n const fields = getFields(\n this.props.savedSearches,\n this.props.resultLayout.indexedSearches[searchIndex]\n ).toJS()\n\n const searchCard = getSearchCard(this.props.resultLayout, searchIndex)\n\n const path = getSelection(fields, searchCard, 'count')\n path[0] = searchIndex //Root select type becomes aliased to the searchIndex\n return path\n }\n\n getPercentValue = data => {\n const denomSelection = this.getSelection('denom', data)\n const numSelection = this.getSelection('num', data)\n\n const total = _.get(data, denomSelection) || 0\n const numerator = _.get(data, numSelection) || 0\n\n if (total === 0) {\n return 0\n }\n\n const value = (numerator / total) * 100\n\n return value > 100 ? 100 : value\n }\n\n getMarkers = () => {\n const { resultLayout } = this.props\n\n const widgetOptions = resultLayout.widgetOptions || {}\n const configuredMarkers = widgetOptions.slices || []\n\n const markers = configuredMarkers.map((slice, index) => {\n const previousVal = index > 0 ? configuredMarkers[index - 1].marker : 0\n return {\n start: previousVal,\n end: slice.marker,\n color: slice.color,\n }\n })\n\n // const gaugeLabels = configuredMarkers.map((slice, index) => {\n // const previousVal = index > 0 ? configuredMarkers[index - 1].marker : 0\n // return `${previousVal}% - ${slice.marker}%`\n // })\n\n return markers\n }\n\n getSearchId = () => {\n const { options, sonraiSearches, resultLayout } = this.props\n let searchObj = Map({ uiSearch: null, advancedSearch: null })\n if (hasSonraiSearch(options, 'sonraionclick')) {\n const searches = getSearchIdForSonraiSearches(options, sonraiSearches)\n searchObj = searchObj.set('advancedSearch', searches)\n } else {\n searchObj = searchObj.set(\n 'uiSearch',\n resultLayout.indexedSearches.onclick\n )\n }\n return searchObj\n }\n\n render() {\n if (this.props.data === undefined) {\n const searchId = this.getSearchId()\n const queryConfig = this.getQuery()\n\n if (!queryConfig.gqlStatement) {\n return (\n \n )\n }\n\n const filtered = queryHasPivotFilter(queryConfig.gqlStatement)\n return (\n \n {({ error, data, refetch, networkStatus }) => {\n const percentValue = this.getPercentValue(data)\n\n return (\n \n {this.props.title}\n \n }\n filtered={filtered}\n error={error}\n description={_.get(this.props.resultLayout, [\n 'widgetOptions',\n 'description',\n ])}\n >\n \n \n \n \n \n {this.props.title}\n \n \n \n )\n }}\n \n )\n } else {\n return (\n \n \n \n \n \n {this.props.title}\n \n \n )\n }\n }\n}\n\nGaugeWidget.propTypes = {\n allowDelete: PropTypes.bool,\n allowUpdate: PropTypes.bool,\n data: PropTypes.number,\n disableToolbar: PropTypes.bool,\n getQueryBuilder: PropTypes.func.isRequired,\n onRemove: PropTypes.func.isRequired,\n options: PropTypes.object,\n resultLayout: PropTypes.shape({\n indexedSearches: PropTypes.objectOf(PropTypes.string).isRequired,\n widgetOptions: PropTypes.shape({\n slices: PropTypes.arrayOf(\n PropTypes.shape({\n marker: PropTypes.number.isRequired,\n color: PropTypes.string,\n })\n ),\n checkBoxValue: PropTypes.string,\n }).isRequired,\n }).isRequired,\n savedSearches: ImmutablePropTypes.map.isRequired,\n static: PropTypes.bool,\n title: PropTypes.string,\n toggleStatic: PropTypes.func.isRequired,\n onClickSearch: PropTypes.func,\n onEdit: PropTypes.func,\n sonraiSearches: ImmutablePropTypes.iterable,\n widget: PropTypes.object,\n}\n\nexport default GaugeWidget\n","import React from 'react'\nimport PropTypes from 'prop-types'\nimport { Input, Label } from 'reactstrap'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport { List } from 'immutable'\nimport { Table, Form } from 'reactstrap'\nimport _ from 'lodash'\n\nimport TextLink from 'components/TextLink'\nimport Button from 'components/Button'\nimport ColorPicker from 'components/ColorPicker'\nimport BorderlessButton from 'components/BorderlessButton'\nimport Icon from 'components/Icon'\nimport CombinedSearches from 'components/CombinedSearches'\nimport FormLabel from 'components/FormLabel'\nimport GaugeWidget from 'components/GaugeWidget'\nimport PreviewContainer from './PreviewContainer'\n\nconst styles = {\n cancelAddSlice: {\n marginLeft: '0.5em',\n },\n container: {\n display: 'grid',\n gridTemplateColumns: '50% 1fr',\n gridColumnGap: '1em',\n },\n sectionTitle: {\n fontSize: '1em',\n fontWeight: '400',\n margin: '0',\n padding: '1em 0 0 0',\n },\n colorContainer: {\n display: 'flex',\n alignItems: 'center',\n },\n addSliceButton: {\n display: 'flex',\n alignItems: 'center',\n },\n addSliceIcon: {\n marginRight: '0.5em',\n fontSize: '20px',\n },\n configs: {\n marginTop: '1em',\n backgroundColor: '#fff',\n borderBottom: '1px solid #dee2e6',\n },\n addMarkerContainer: {\n display: 'grid',\n gridTemplateColumns: '30% 1fr',\n padding: '0.5em 0',\n },\n markerInput: {\n maxWidth: '100px',\n },\n wrapperWithPreview: {\n display: 'grid',\n gridTemplateColumns: '35% 1fr',\n gridColumnGap: '2em',\n },\n config: {\n display: 'grid',\n gridTemplateColumns: '1fr 1fr',\n gridColumnGap: '2em',\n },\n}\n\nexport class GaugeConfig extends React.Component {\n constructor(props) {\n super(props)\n\n !this.props.editMode\n ? this.props.setWidgetOptions({\n slices: [{ marker: 100 }],\n checkBoxValue: '',\n })\n : null\n\n this.state = {\n showAddSlice: false,\n newSliceMarker: '',\n newSliceColor: '',\n denom: false,\n num: false,\n }\n }\n\n componentDidUpdate() {\n this.updateValidity()\n }\n\n updateValidity = () => {\n const hasNumSonraiSearch = !!this.props.widgetSonraiSearches.num\n const hasNumSavedSearch =\n !!this.props.widgetSavedSearches.num && !!this.props.widgetSearchCards.num\n\n const hasDenomSonraiSearch = !!this.props.widgetSonraiSearches.denom\n const hasDenomSavedSearch =\n !!this.props.widgetSavedSearches.denom &&\n !!this.props.widgetSearchCards.denom\n\n const hasValidNumSearch = hasNumSavedSearch || hasNumSonraiSearch\n const hasValidDenomSearch = hasDenomSavedSearch || hasDenomSonraiSearch\n\n const hasTitle = this.props.widgetTitle\n\n const valid = hasValidNumSearch && hasValidDenomSearch && hasTitle\n this.props.setValidity(valid)\n }\n\n setTitle = e => {\n this.props.setWidgetTitle(e.target.value)\n }\n\n setDescription = e => {\n const string = e.target.value\n\n this.props.setWidgetOptions({\n description: string,\n })\n }\n\n setNumeratorSearch = option => {\n this.props.setWidgetSavedSearch(option ? option.value : null, 'num')\n }\n\n setDenominatorSearch = option => {\n this.props.setWidgetSavedSearch(option ? option.value : null, 'denom')\n }\n\n setOnclickSearch = option => {\n this.props.setWidgetSavedSearch(option ? option.value : null, 'onclick')\n }\n\n setSonraiOnclickSearch = selectedOption => {\n this.props.setWidgetSonraiSearch(\n selectedOption ? selectedOption.value : null,\n 'sonraionclick'\n )\n }\n\n setNumeratorField = option => {\n this.props.setWidgetSearchField(option ? option.value : null, 'num')\n }\n\n setDenominatorField = option => {\n this.props.setWidgetSearchField(option ? option.value : null, 'denom')\n }\n\n setSonraiSearchDenom = selectedOption => {\n this.props.setWidgetSonraiSearch(\n selectedOption ? selectedOption.value : null,\n 'denom'\n )\n }\n\n setSonraiSearchNum = selectedOption => {\n this.props.setWidgetSonraiSearch(\n selectedOption ? selectedOption.value : null,\n 'num'\n )\n }\n\n editSliceColor = (sliceId, newColor) => {\n const slices = [...this.props.widgetOptions.slices]\n slices[sliceId].color = newColor\n\n this.props.setWidgetOptions({\n ...this.props.widgetOptions,\n slices,\n })\n }\n\n renderSlicesConfig = () => {\n const slices = this.props.widgetOptions.slices || []\n return (\n \n \n {slices.map((slice, index) => (\n \n \n = 100}\n onClick={() => this.removeMarker(index)}\n >\n \n \n | \n Ending at {slice.marker}% | \n\n \n \n | \n
\n ))}\n \n
\n )\n }\n\n removeMarker = index => {\n const slices = [...this.props.widgetOptions.slices]\n\n if (slices[index].marker === 100) {\n return\n }\n\n slices.splice(index, 1)\n this.props.setWidgetOptions({\n ...this.props.widgetOptions,\n slices,\n })\n }\n\n addMarker = () => {\n const value = parseInt(this.state.newSliceMarker)\n if (value < 0 || value > 100) {\n return\n }\n\n const color = this.state.newSliceColor\n\n const slices = [...this.props.widgetOptions.slices]\n\n slices.push({\n marker: value,\n color,\n })\n\n slices.sort((a, b) => a.marker - b.marker)\n\n this.props.setWidgetOptions({\n ...this.props.widgetOptions,\n slices,\n })\n\n this.resetAddSlice()\n }\n\n toggleAddSlice = () => {\n this.setState(state => ({\n showAddSlice: !state.showAddSlice,\n }))\n }\n\n resetAddSlice = () => {\n this.setState({\n showAddSlice: false,\n newSliceMarker: '',\n newSliceColor: '',\n })\n }\n\n setNewSliceMarker = e => {\n const intValue = parseInt(e.target.value)\n if (intValue > 100 || intValue < 0) {\n return\n }\n\n this.setState({\n newSliceMarker: e.target.value,\n })\n }\n\n onPickColor = color => {\n this.setState({\n newSliceColor: color,\n })\n }\n\n renderAddNewSlice = () => {\n return (\n \n )\n }\n\n render() {\n const denominatorSearchId = this.props.widgetSavedSearches['denom']\n const denominatorField = this.props.widgetSearchCards['denom']\n\n const numeratorSearchId = this.props.widgetSavedSearches['num']\n const numeratorField = this.props.widgetSearchCards['num']\n\n const onclickSearchId = this.props.widgetSavedSearches['onclick']\n const sonraiOnclickSearch = this.props.widgetSonraiSearches['sonraionclick']\n\n const numeratorSonraiSearchName = this.props.widgetSonraiSearches['num']\n const denomSonraiSearchName = this.props.widgetSonraiSearches['denom']\n\n const denomSearchFieldsList = this.props.searchCards.get(\n denominatorSearchId,\n List()\n )\n const numSearchFieldsList = this.props.searchCards.get(\n numeratorSearchId,\n List()\n )\n\n return (\n \n {this.props.previewWidget && (\n
\n \n Widget Title\n )\n }\n />\n
\n \n )}\n\n
\n
\n )\n }\n}\n\nGaugeConfig.propTypes = {\n editMode: PropTypes.bool,\n getQueryBuilder: PropTypes.func,\n previewWidget: PropTypes.bool,\n savedSearches: ImmutablePropTypes.mapOf(\n ImmutablePropTypes.contains({\n sid: PropTypes.string,\n name: PropTypes.string,\n })\n ),\n searchCards: ImmutablePropTypes.mapOf(\n ImmutablePropTypes.contains({\n id: PropTypes.string,\n name: PropTypes.string,\n })\n ),\n setWidgetOptions: PropTypes.func.isRequired,\n setWidgetSavedSearch: PropTypes.func.isRequired,\n setWidgetSearchField: PropTypes.func.isRequired,\n setWidgetTitle: PropTypes.func.isRequired,\n setValidity: PropTypes.func.isRequired,\n widgetSavedSearches: PropTypes.objectOf(PropTypes.array),\n widgetSearchCards: PropTypes.objectOf(PropTypes.object),\n widgetTitle: PropTypes.string,\n widgetOptions: PropTypes.shape({\n slices: PropTypes.array,\n checkBoxValue: PropTypes.string,\n description: PropTypes.string,\n }).isRequired,\n widgetSonraiSearches: PropTypes.object,\n savedSonraiSearches: ImmutablePropTypes.iterableOf(\n ImmutablePropTypes.contains({\n sid: PropTypes.string,\n name: PropTypes.string,\n })\n ),\n setWidgetSonraiSearch: PropTypes.func,\n}\n\nexport default GaugeConfig\n","import React, { Fragment } from 'react'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport PropTypes from 'prop-types'\nimport { Input } from 'reactstrap'\nimport { List } from 'immutable'\nimport CombinedSearches from 'components/CombinedSearches'\nimport FormLabel from 'components/FormLabel'\nimport { REGION_MAP_SEARCH_NAME, SUPPORT_EMAIL } from 'appConstants'\n\nexport class RegionsWidgetConfig extends React.Component {\n componentDidUpdate() {\n this.updateValidity()\n }\n\n updateValidity = () => {\n const hasSonraiSearch = !!this.props.widgetSonraiSearches.search\n const hasSavedSearch = !!this.props.widgetSavedSearches.search\n const hasTitle = this.props.widgetTitle\n\n const valid = (hasSonraiSearch || hasSavedSearch) && hasTitle\n this.props.setValidity(valid)\n }\n\n setTitle = e => {\n this.props.setWidgetTitle(e.target.value)\n }\n\n setDescription = e => {\n const string = e.target.value\n\n this.props.setWidgetOptions({\n description: string,\n })\n }\n\n setSubTitle = e => {\n this.props.setWidgetSubTitle(e.target.value)\n }\n\n setSavedSearch = selectedOption => {\n this.props.setWidgetSavedSearch(\n selectedOption ? selectedOption.value : null,\n 'search'\n )\n }\n\n selectSearchField = selectedOption => {\n this.props.setWidgetSearchField(\n selectedOption ? selectedOption.value : null,\n 'search'\n )\n }\n\n setSonraiSearch = selectedOption => {\n this.props.setWidgetSonraiSearch(\n selectedOption ? selectedOption.value : null,\n 'search'\n )\n }\n\n render() {\n const sonraiSearchName = this.props.widgetSonraiSearches['search']\n\n const savedSearchId = this.props.widgetSavedSearches['search']\n const savedSearchField = this.props.widgetSearchCards['search']\n const searchFieldsList = this.props.searchCards.get(\n this.props.widgetSavedSearches['search'],\n List()\n )\n\n return (\n \n \n \n\n \n \n\n \n \n\n \n \n Note: This widget only supports resources with a region property. For\n help, please contact support\n
\n \n search.get('name').includes(REGION_MAP_SEARCH_NAME)\n )}\n setSonraiSearch={this.setSonraiSearch}\n selectedSonraiValue={sonraiSearchName}\n name=\"widgetSearch\"\n savedSearches={this.props.savedSearches}\n selectedSearchId={savedSearchId}\n selectSavedSearch={this.setSavedSearch}\n savedSearchDisabled={!!sonraiSearchName}\n selectedFieldId={savedSearchField ? savedSearchField.id : null}\n searchCards={searchFieldsList}\n searchFieldDisabled={searchFieldsList.isEmpty()}\n selectSearchField={this.selectSearchField}\n searchFieldRequired\n />\n \n )\n }\n}\n\nRegionsWidgetConfig.propTypes = {\n savedSearches: ImmutablePropTypes.iterable,\n savedSonraiSearches: ImmutablePropTypes.iterableOf(\n ImmutablePropTypes.contains({\n sid: PropTypes.string,\n name: PropTypes.string,\n })\n ),\n searchCards: ImmutablePropTypes.map,\n setWidgetOptions: PropTypes.func,\n setWidgetSavedSearch: PropTypes.func,\n setWidgetSearchField: PropTypes.func,\n setWidgetSonraiSearch: PropTypes.func,\n setWidgetSubTitle: PropTypes.func,\n setWidgetTitle: PropTypes.func,\n setValidity: PropTypes.func,\n widgetOptions: PropTypes.shape({\n description: PropTypes.string,\n }),\n widgetSavedSearches: PropTypes.objectOf(PropTypes.array),\n widgetSearchCards: PropTypes.objectOf(PropTypes.object),\n widgetSonraiSearches: PropTypes.object,\n widgetSubTitle: PropTypes.string,\n widgetTitle: PropTypes.string,\n}\n\nexport default RegionsWidgetConfig\n","import React from 'react'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport PropTypes from 'prop-types'\nimport { Input } from 'reactstrap'\nimport { List } from 'immutable'\nimport CombinedSearches from 'components/CombinedSearches'\nimport FormLabel from 'components/FormLabel'\n\nexport class TableConfig extends React.Component {\n componentDidUpdate() {\n this.updateValidity()\n }\n\n updateValidity = () => {\n const hasSonraiSearch = !!this.props.widgetSonraiSearches.table\n const hasSavedSearch = !!this.props.widgetSavedSearches.table\n const hasTitle = this.props.widgetTitle\n\n const valid = (hasSonraiSearch || hasSavedSearch) && hasTitle\n this.props.setValidity(valid)\n }\n\n setTitle = e => {\n this.props.setWidgetTitle(e.target.value)\n }\n\n setDescription = e => {\n const string = e.target.value\n\n this.props.setWidgetOptions({\n description: string,\n })\n }\n\n setSubTitle = e => {\n this.props.setWidgetSubTitle(e.target.value)\n }\n\n setSavedSearch = selectedOption => {\n this.props.setWidgetSavedSearch(\n selectedOption ? selectedOption.value : null,\n 'table'\n )\n }\n\n setSonraiSearch = selectedOption => {\n this.props.setWidgetSonraiSearch(\n selectedOption ? selectedOption.value : null,\n 'table'\n )\n }\n\n setSearchField = selectedOption => {\n this.props.setWidgetSearchField(\n selectedOption ? selectedOption.value : null,\n 'table'\n )\n }\n\n render() {\n const sonraiSearchName = this.props.widgetSonraiSearches['table']\n const savedSearchId = this.props.widgetSavedSearches['table']\n const savedSearchField = this.props.widgetSearchCards['table']\n const searchFieldsList = this.props.searchCards.get(\n this.props.widgetSavedSearches['table'],\n List()\n )\n\n return (\n \n \n \n\n \n \n\n \n \n\n \n \n
\n )\n }\n}\n\nTableConfig.propTypes = {\n savedSearches: ImmutablePropTypes.iterableOf(\n ImmutablePropTypes.contains({\n sid: PropTypes.string,\n name: PropTypes.string,\n })\n ),\n savedSonraiSearches: ImmutablePropTypes.iterableOf(\n ImmutablePropTypes.contains({\n sid: PropTypes.string,\n name: PropTypes.string,\n })\n ),\n searchCards: ImmutablePropTypes.mapOf(\n PropTypes.oneOfType([\n ImmutablePropTypes.contains({\n id: PropTypes.string,\n name: PropTypes.string,\n }),\n PropTypes.array,\n ])\n ),\n setWidgetOptions: PropTypes.func,\n setWidgetSavedSearch: PropTypes.func,\n setWidgetSonraiSearch: PropTypes.func,\n setWidgetSearchField: PropTypes.func,\n setWidgetSubTitle: PropTypes.func,\n setWidgetTitle: PropTypes.func,\n setValidity: PropTypes.func,\n widgetOptions: PropTypes.shape({\n description: PropTypes.string,\n }),\n widgetSavedSearches: PropTypes.object,\n widgetSonraiSearches: PropTypes.object,\n widgetSearchCards: PropTypes.object,\n widgetSubTitle: PropTypes.string,\n widgetTitle: PropTypes.string,\n}\n\nexport default TableConfig\n","/**\n *\n * ListWidget\n *\n */\n\nimport React from 'react'\nimport PropTypes from 'prop-types'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport _ from 'lodash'\nimport { Map } from 'immutable'\nimport { Query } from 'react-apollo'\nimport gql from 'graphql-tag'\nimport TextLink from 'components/TextLink'\nimport Card, { TopTitle, CardBody } from 'components/Card'\nimport WidgetCard from 'components/WidgetCard'\nimport {\n getFields,\n getTableSavedSearchQuery,\n getSonraiSearchQuery,\n getSearchCard,\n hasSonraiSearch,\n getSonraiSearchData,\n getSelection,\n getWidgetResultsExplorerSavedSearchQuery,\n} from 'query-builder'\nimport { getTypeFromSrn } from 'utils/graphDataUtils'\nimport { getSearchIdForSonraiSearches } from 'utils/sonraiUtils'\nimport themeable, { themeShape } from 'containers/ThemeManager/Themeable'\nimport { queryHasPivotFilter } from 'query-builder'\nimport EmptyWidgetResults from 'components/EmptyWidgetResults'\nimport { exists } from 'utils/sonraiUtils'\n\nconst styles = {\n title: {\n fontSize: '20px',\n fontWeight: '300',\n borderBottom: '1px solid #eee',\n cursor: 'pointer',\n marginRight: '0px',\n },\n containerStyle: {\n overflow: 'auto',\n },\n listContainer: {\n alignItems: 'start',\n },\n list: {\n margin: 0,\n padding: 0,\n width: '100%',\n },\n listItem: {\n listStyleType: 'none',\n padding: '0.5em 1em',\n fontSize: '14px',\n },\n listNumber: {\n color: '#888',\n fontSize: '1.2em',\n },\n noitems: {\n color: '#888',\n textAlign: 'center',\n marginTop: '1em',\n },\n}\n\nclass ListWidget extends React.Component {\n constructor(props) {\n super(props)\n\n this.state = {\n hoveredId: null,\n }\n }\n\n renderListItems = data => {\n if (!data || data.length === 0) {\n return \n }\n\n let numberOfItems =\n this.props.resultLayout &&\n exists(this.props.resultLayout.widgetOptions.items)\n ? this.props.resultLayout.widgetOptions.items\n : data.length\n\n let cutArr = data.slice(0, numberOfItems)\n return cutArr.map((item, index) => (\n \n {index + 1}. \n \n {item.label}\n \n \n ))\n }\n\n setHover = newId => {\n this.setState({\n hoveredId: newId,\n })\n }\n\n handleOnClick = srn => {\n if (srn && this.props.onClickNodeView) {\n this.props.onClickNodeView(srn, getTypeFromSrn(srn))\n }\n }\n\n getQuery = () => {\n if (hasSonraiSearch(this.props.options, 'list')) {\n return getSonraiSearchQuery(this.props.options.sonraiSearches.list)\n } else {\n return getTableSavedSearchQuery(\n this.props.getQueryBuilder,\n this.props.savedSearches,\n this.props.resultLayout.indexedSearches.list,\n { limit: this.props.resultLayout.widgetOptions.items }\n )\n }\n }\n\n getData = data => {\n if (!_.isEmpty(this.props.options.sonraiSearches.list)) {\n return this.getDataFromSonraiSearch(data)\n } else {\n const fields = getFields(\n this.props.savedSearches,\n this.props.resultLayout.indexedSearches.list\n ).toJS()\n const searchCard = getSearchCard(this.props.resultLayout, 'list')\n const selection = getSelection(fields, searchCard, 'items')\n const savedSearchResults = _.get(data, selection) || []\n\n const property = this.props.resultLayout.widgetOptions.properties.value\n\n return savedSearchResults.map(item => ({\n label: item[property],\n srn: item.srn,\n }))\n }\n }\n\n getDataFromSonraiSearch = data => {\n const searchData = getSonraiSearchData(data)\n if (searchData.items) {\n const property = this.props.resultLayout.widgetOptions.properties.value\n return searchData.items.map(item => ({\n label: item[property],\n srn: item.srn,\n }))\n }\n }\n\n getSearchName = () => {\n if (!this.props.savedSearches) {\n return\n }\n\n return hasSonraiSearch(this.props.options, 'list')\n ? this.props.options.sonraiSearches.list\n : this.props.savedSearches.getIn([\n this.props.resultLayout.indexedSearches.list,\n 'name',\n ])\n }\n\n getExploreResultsQuery = () => {\n if (hasSonraiSearch(this.props.options, 'list')) {\n return getSonraiSearchQuery(this.props.options.sonraiSearches.list)\n } else {\n return getWidgetResultsExplorerSavedSearchQuery(\n this.props.getQueryBuilder,\n this.props.savedSearches,\n this.props.resultLayout.indexedSearches.list\n )\n }\n }\n\n handleOnClickSearch = () => {\n this.props.onClickSearch({\n savedSearchId: this.props.resultLayout.indexedSearches.list,\n sonraiSearchName: this.props.options.sonraiSearches.list,\n searchTitle: this.props.title,\n })\n }\n\n getSearchId = () => {\n const { options, sonraiSearches, resultLayout } = this.props\n let searchObj = Map({ uiSearch: null, advancedSearch: null })\n if (hasSonraiSearch(options, 'list')) {\n const searches = getSearchIdForSonraiSearches(options, sonraiSearches)\n searchObj = searchObj.set('advancedSearch', searches)\n } else {\n searchObj = searchObj.set('uiSearch', resultLayout.indexedSearches.list)\n }\n return searchObj\n }\n\n render() {\n if (this.props.data === undefined) {\n const queryConfig = this.getQuery()\n if (!queryConfig.gqlStatement) {\n return \n }\n const searchId = this.getSearchId()\n const filtered = queryHasPivotFilter(queryConfig.gqlStatement)\n\n return (\n \n {({ error, data, refetch, networkStatus }) => {\n const values = this.getData(data)\n return (\n \n \n \n {this.props.title}\n \n \n 0 ? styles.listContainer : {}\n }\n >\n {this.renderListItems(values)}
\n \n \n )\n }}\n \n )\n } else {\n const items =\n this.props.data.length > 0 && typeof this.props.data[0] === 'object'\n ? this.props.data\n : this.props.data.map(data => ({ label: data, srn: null }))\n return (\n \n \n \n {this.props.title}\n \n {' '}\n 0 ? styles.listContainer : {}}\n >\n {this.renderListItems(items)}
\n \n \n )\n }\n }\n}\n\nListWidget.propTypes = {\n allowDelete: PropTypes.bool,\n allowUpdate: PropTypes.bool,\n disableToolbar: PropTypes.bool,\n title: PropTypes.string.isRequired,\n onRemove: PropTypes.func,\n theme: themeShape,\n toggleStatic: PropTypes.func,\n savedSearches: ImmutablePropTypes.map,\n static: PropTypes.bool,\n sonraiSearches: ImmutablePropTypes.iterable.isRequired,\n onClickNodeView: PropTypes.func,\n options: PropTypes.object,\n resultLayout: PropTypes.object,\n getQueryBuilder: PropTypes.func,\n data: PropTypes.array,\n onClickSearch: PropTypes.func,\n onEdit: PropTypes.func,\n widget: PropTypes.object,\n}\n\nexport default themeable(ListWidget)\n","import React from 'react'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport PropTypes from 'prop-types'\nimport { Input } from 'reactstrap'\nimport { Query } from 'react-apollo'\nimport gql from 'graphql-tag'\nimport SelectBar from 'components/SelectBar'\nimport _ from 'lodash'\nimport { List } from 'immutable'\nimport CombinedSearches from 'components/CombinedSearches'\nimport FormLabel from 'components/FormLabel'\nimport { QueryBuilder } from 'query-builder'\nimport {\n getFields,\n getTableSavedSearchQuery,\n getSelection,\n getSonraiSearchQueryString,\n getSonraiSearchData,\n} from 'query-builder'\nimport ListWidget from 'components/ListWidget'\nimport PreviewContainer from './PreviewContainer'\n\nconst styles = {\n wrapperWithPreview: {\n display: 'grid',\n gridTemplateColumns: '35% 1fr',\n gridColumnGap: '2em',\n },\n}\n\nclass ListWidgetConfig extends React.Component {\n componentDidUpdate() {\n this.updateValidity()\n }\n\n updateValidity = () => {\n const hasSonraiSearch = !!this.props.widgetSonraiSearches.list\n const hasSavedSearch =\n !!this.props.widgetSavedSearches.list &&\n !!this.props.widgetSearchCards.list\n const hasTitle = this.props.widgetTitle\n\n const hasDisplayField = !!this.props.widgetOptions.properties\n\n const valid =\n (hasSonraiSearch || hasSavedSearch) && hasTitle && hasDisplayField\n\n this.props.setValidity(valid)\n }\n\n setTitle = e => {\n this.props.setWidgetTitle(e.target.value)\n }\n\n setDescription = e => {\n const string = e.target.value\n\n this.props.setWidgetOptions({\n description: string,\n })\n }\n\n setSavedSearch = selectedOption => {\n this.props.setWidgetSavedSearch(\n selectedOption ? selectedOption.value : null,\n 'list'\n )\n }\n\n setSearchField = selectedOption => {\n this.props.setWidgetSearchField(\n selectedOption ? selectedOption.value : null,\n 'list'\n )\n }\n\n setSonraiSearch = selectedValue => {\n this.props.setWidgetSonraiSearch(\n selectedValue ? selectedValue.value : undefined,\n 'list'\n )\n }\n\n setNumberOfItems = e => {\n this.props.setWidgetOptions({\n ...this.props.widgetOptions,\n items: e.target.value,\n })\n }\n\n setListField = selectedOption => {\n this.props.setWidgetOptions({\n ...this.props.widgetOptions,\n properties: selectedOption,\n })\n }\n\n getQueryBuilder = query => {\n return new QueryBuilder({\n query,\n types: this.props.queryTypes,\n })\n }\n\n getSearchQuery = () => {\n if (this.props.widgetSonraiSearches.list) {\n return getSonraiSearchQueryString(this.props.widgetSonraiSearches.list)\n } else if (\n this.props.widgetSavedSearches['list'] &&\n !!this.getSearchCard()\n ) {\n const queryConfig = getTableSavedSearchQuery(\n this.getQueryBuilder,\n this.props.savedSearches,\n this.props.widgetSavedSearches['list'],\n { limit: 1 }\n )\n\n return queryConfig.gqlStatement\n }\n }\n\n getSearchCard = () => {\n return this.props.widgetSearchCards['list']\n }\n\n getSampleItemFromData = data => {\n if (this.props.widgetSonraiSearches.list) {\n const results = getSonraiSearchData(data).items\n return results ? results[0] : {}\n } else {\n const fields = getFields(\n this.props.savedSearches,\n this.props.widgetSavedSearches['list']\n ).toJS()\n\n const path = getSelection(fields, this.getSearchCard(), 'items')\n\n const results = _.get(data, path) || []\n return results.length > 0 ? results[0] : {}\n }\n }\n\n renderPropertyDropDown = () => {\n const query = this.getSearchQuery()\n\n if (!query) {\n return (\n \n )\n }\n\n return (\n \n {({ data, networkStatus }) => {\n if (networkStatus === 1) {\n return (\n \n )\n }\n\n if (!_.isEmpty(data)) {\n const item = this.getSampleItemFromData(data)\n\n if (!_.isEmpty(item)) {\n const fields = Object.keys(item).map(val => ({\n label: val,\n value: val,\n }))\n\n return (\n \n )\n }\n }\n\n return (\n \n )\n }}\n \n )\n }\n\n render() {\n const sonraiSearchName = this.props.widgetSonraiSearches['list']\n const savedSearchId = this.props.widgetSavedSearches['list']\n const savedSearchField = this.props.widgetSearchCards['list']\n const searchFieldsList = this.props.searchCards.get(\n this.props.widgetSavedSearches['list'],\n List()\n )\n\n return (\n \n )\n }\n}\n\nListWidgetConfig.propTypes = {\n getQueryBuilder: PropTypes.func.isRequired,\n previewWidget: PropTypes.bool,\n queryTypes: ImmutablePropTypes.map,\n savedSearches: ImmutablePropTypes.mapOf(\n ImmutablePropTypes.contains({\n sid: PropTypes.string,\n name: PropTypes.string,\n })\n ),\n savedSonraiSearches: ImmutablePropTypes.list,\n searchCards: ImmutablePropTypes.mapOf(\n ImmutablePropTypes.contains({\n id: PropTypes.string,\n name: PropTypes.string,\n })\n ),\n setWidgetSavedSearch: PropTypes.func,\n setWidgetSearchField: PropTypes.func,\n setWidgetSonraiSearch: PropTypes.func,\n setWidgetOptions: PropTypes.func,\n setWidgetTitle: PropTypes.func,\n setValidity: PropTypes.func,\n widgetOptions: PropTypes.shape({\n items: PropTypes.array,\n properties: PropTypes.array,\n description: PropTypes.string,\n }).isRequired,\n widgetSavedSearches: PropTypes.objectOf(PropTypes.array),\n widgetSearchCards: PropTypes.objectOf(PropTypes.object),\n widgetSonraiSearches: PropTypes.object,\n widgetTitle: PropTypes.string,\n}\n\nexport default ListWidgetConfig\n","import React, { Fragment } from 'react'\nimport PropTypes from 'prop-types'\nimport ApexChart from 'react-apexcharts'\nimport _ from 'lodash'\nimport moment from 'moment'\nimport Scrollable from 'components/Scrollable'\nimport themeable, { themeShape } from 'containers/ThemeManager/Themeable'\nimport EmptyWidgetResults from 'components/EmptyWidgetResults'\nimport { colorForEntityString, stripTags } from 'utils/sonraiUtils'\n\nconst PIE_LEGEND_HEIGHT = 45\n\nexport class SizedChart extends React.Component {\n styles = {\n legendColor: {\n width: '10px',\n height: '10px',\n borderRadius: '50%',\n marginTop: '4px',\n },\n legendEntry: {\n display: 'inline-grid',\n padding: '0 0.3em',\n fontSize: '0.9em',\n wordBreak: 'break-word',\n gridTemplateColumns: '10px 1fr',\n gridColumnGap: '0.5em',\n },\n }\n pointsAreTheSame = () => {\n return _.uniq(this.props.yValues).length === 1\n }\n\n getBarPieOptions = () => {\n const maxYValue = _.max(this.props.yValues)\n const numGridLines = maxYValue < 6 ? maxYValue + 1 : 6 //Default value is 6\n\n return {\n yaxis: {\n //If the yValues are all less than 6, apexgrid will, by default, duplicate them to get 6 gridlines\n tickAmount: numGridLines,\n },\n width: this.props.width,\n chart: {\n toolbar: {\n show: false,\n },\n animations: {\n enabled: !this.props.static,\n },\n events: {\n mounted: this.props.onChartReady,\n beforeMount: apexChartInstance => {\n this.apexChartInstance = apexChartInstance\n },\n dataPointSelection: (e, chart, options) => {\n if (\n (this.props.onClickPoint &&\n this.props.yLabels[options.dataPointIndex] !== 'null') ||\n this.props.yLabels[options.dataPointIndex] !== '-'\n ) {\n this.props.onClickPoint({\n values: this.props.yValues,\n labels: this.props.yLabels,\n index: options.dataPointIndex,\n })\n }\n },\n },\n },\n dataLabels: {\n enabled: false,\n },\n xaxis: {\n categories: this.props.yLabels ? this.props.yLabels : [],\n },\n colors: this.props.colors || this.getPieBarChartColors(),\n labels: this.props.yLabels ? this.props.yLabels : [],\n legend: {\n show: false,\n },\n states: {\n active: {\n filter: 'lighten',\n },\n },\n plotOptions: {\n bar: {\n distributed: true,\n },\n },\n }\n }\n\n getRadialBarOptions = () => {\n return {\n chart: {\n type: 'radialBar',\n events: {\n mounted: this.props.onChartReady,\n },\n animations: {\n enabled: !this.props.static,\n },\n },\n colors: [this.props.theme.secondary],\n plotOptions: {\n radialBar: {\n offsetX: 0,\n offsetY: 0,\n size: undefined,\n startAngle: -135,\n endAngle: 135,\n track: {\n background: '#eaeef3',\n startAngle: -135,\n endAngle: 135,\n padding: 20,\n },\n dataLabels: {\n name: {\n show: false,\n },\n value: {\n fontSize: '20px',\n offsetY: 7,\n show: true,\n },\n },\n },\n },\n fill: {\n type: 'gradient',\n gradient: {\n shade: 'light',\n gradientToColors: [this.props.theme.secondary],\n stops: [0, 100],\n },\n },\n stroke: {\n lineCap: 'butt',\n },\n }\n }\n\n getSparkOptions = () => {\n return {\n chart: {\n toolbar: {\n show: false,\n },\n zoom: {\n enabled: false,\n },\n events: {\n mounted: this.props.onChartReady,\n },\n animations: {\n enabled: !this.props.static,\n },\n },\n markers: {\n size: this.props.xLabels.length <= 1 ? 5 : 0,\n hover: {\n size: this.props.xLabels.length <= 1 ? 6 : 5,\n },\n },\n grid: {\n clipMarkers: false,\n padding: {\n top: -25,\n bottom: -25,\n left: 20,\n right: 20,\n },\n xaxis: {\n lines: {\n show: true,\n },\n },\n yaxis: {\n lines: {\n show: this.props.xLabels.length <= 1 ? true : false,\n },\n },\n },\n stroke: {\n width: 3,\n curve: 'smooth',\n },\n xaxis: {\n min:\n this.props.xLabels.length <= 1\n ? moment(this.props.xLabels[0]).subtract(1, 'd').format('LLL')\n : undefined,\n max:\n this.props.xLabels.length <= 1\n ? moment(this.props.xLabels[0]).add(1, 'd').format('LLL')\n : undefined,\n axisBorder: {\n show: this.props.yValues.length <= 1 ? true : false,\n },\n tooltip: {\n enabled: false,\n },\n axisTicks: {\n show: false,\n },\n type: this.props.xLabels.length <= 1 ? '' : 'datetime',\n categories: this.props.xLabels ? this.props.xLabels : [],\n labels: {\n offsetY: this.props.xLabels.length <= 1 ? -4 : -5,\n },\n },\n yaxis: {\n min: this.props.minY,\n max: this.props.yValues.length <= 1 ? 100 : this.props.maxY,\n show: this.props.yValues.length <= 1 ? true : false,\n axisTicks: {\n show: false,\n },\n axisBorder: {\n show: this.props.yValues.length <= 1 ? true : false,\n },\n labels: {\n show: false,\n },\n },\n colors: [this.props.theme.secondary, ...this.props.theme.chartColors],\n fill: {\n type: this.pointsAreTheSame() ? 'solid' : 'gradient', //If all the data values are the same, for some reason using 'gradient' will not draw a line at all\n gradient: {\n gradientToColors: [this.props.theme.emphasis],\n shade: 'light',\n type: 'vertical',\n stops: [0, 100, 100, 100],\n },\n },\n tooltip: {\n marker: {\n show: false,\n },\n fixed: {\n enabled: false,\n },\n x: {\n show: false,\n },\n y: {\n title: {\n formatter: seriesName => seriesName,\n },\n },\n },\n }\n }\n\n getOptionsForType = () => {\n if (this.props.type === 'spark') {\n return this.getSparkOptions()\n }\n\n if (this.props.type === 'radialBar') {\n return this.getRadialBarOptions()\n }\n\n return this.getBarPieOptions()\n }\n\n getOptions = () => {\n const defaultOptions = this.getOptionsForType()\n\n return _.merge(defaultOptions, this.props.options)\n }\n\n getSeries = () => {\n if (this.props.type === 'spark') {\n return [\n {\n name: stripTags(this.props.yTitle),\n data: this.props.yValues,\n },\n ]\n }\n\n if (this.props.type === 'radialBar') {\n return this.props.yValues\n }\n\n if (this.props.type === 'bar') {\n return [\n {\n name: stripTags(this.props.yTitle),\n data: this.props.yValues,\n },\n ]\n }\n\n return this.props.yValues\n }\n\n getType = () => {\n if (this.props.type === 'spark') {\n return 'line'\n }\n\n return this.props.type\n }\n\n onHoverLegend = e => {\n let series = this.apexChartInstance.series\n series.toggleSeriesOnHover(e, e.target)\n }\n\n getPieBarChartColors = () => {\n return this.props.yLabels.map(label => {\n return colorForEntityString(label, this.props.typeColors)\n })\n }\n\n renderScrollableLegend = () => {\n const colors = this.props.colors || this.getPieBarChartColors()\n\n return (\n \n {this.props.yLabels.map((label, index) => (\n \n ))}\n \n )\n }\n\n hasCustomLegend = () => {\n return this.props.type === 'donut'\n }\n\n render() {\n const chartHeight = this.hasCustomLegend()\n ? this.props.height - PIE_LEGEND_HEIGHT\n : this.props.height\n\n return (\n \n \n {this.props.yValues.length === 0 ? (\n
\n ) : (\n
\n )}\n
\n {this.props.type === 'donut' && this.renderScrollableLegend()}\n \n )\n }\n}\n\nSizedChart.propTypes = {\n yTitle: '',\n yLabels: [],\n options: {},\n chartWrapperStyle: {},\n}\n\nSizedChart.propTypes = {\n colors: PropTypes.arrayOf(PropTypes.string),\n yTitle: PropTypes.string,\n yLabels: PropTypes.arrayOf(PropTypes.string),\n yValues: PropTypes.arrayOf(PropTypes.number),\n xLabels: PropTypes.arrayOf(PropTypes.string),\n onClickPoint: PropTypes.func,\n onChartReady: PropTypes.func,\n options: PropTypes.object,\n static: PropTypes.bool,\n theme: themeShape,\n type: PropTypes.oneOf(['bar', 'pie', 'donut', 'spark', 'radialBar'])\n .isRequired,\n typeColors: PropTypes.object,\n width: PropTypes.number.isRequired,\n height: PropTypes.number.isRequired,\n minY: PropTypes.number,\n maxY: PropTypes.number,\n}\n\nexport default themeable(SizedChart)\n","import React from 'react'\nimport PropTypes from 'prop-types'\nimport ContainerDimensions from 'react-container-dimensions'\nimport SizedChart from './SizedChart'\n\nexport class Chart extends React.Component {\n render() {\n return (\n \n {this.props.type !== 'radialBar' ? (\n \n \n \n ) : (\n \n )}\n
\n )\n }\n}\n\nChart.propTypes = {\n yTitle: '',\n}\n\nChart.propTypes = {\n yTitle: PropTypes.string,\n yLabels: PropTypes.arrayOf(PropTypes.string),\n yValues: PropTypes.arrayOf(PropTypes.number),\n onClickPoint: PropTypes.func,\n type: PropTypes.oneOf(['bar', 'pie', 'donut', 'spark', 'radialBar'])\n .isRequired,\n}\n\nexport default Chart\n","/**\n *\n * PieChartWidget\n *\n */\n\nimport React from 'react'\nimport PropTypes from 'prop-types'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport _ from 'lodash'\nimport { Query } from 'react-apollo'\nimport gql from 'graphql-tag'\nimport { Map } from 'immutable'\nimport TextLink from 'components/TextLink'\nimport Chart from 'components/Chart'\nimport Card, { CardBody, BottomTitle } from 'components/Card'\nimport WidgetCard from 'components/WidgetCard'\nimport {\n getFields,\n getGroupedSavedSearchQuery,\n getSearchCard,\n hasSonraiSearch,\n getSelection,\n getSonraiSearchQuery,\n getSonraiSearchData,\n} from 'query-builder'\nimport { queryHasPivotFilter } from 'query-builder'\nimport { flattenData, getSearchIdForSonraiSearches } from 'utils/sonraiUtils'\n\nclass PieChartWidget extends React.Component {\n getQuery = () => {\n if (hasSonraiSearch(this.props.options, 'pie')) {\n return getSonraiSearchQuery(this.props.options.sonraiSearches.pie)\n } else {\n return getGroupedSavedSearchQuery(\n this.props.getQueryBuilder,\n this.props.savedSearches,\n this.props.resultLayout.indexedSearches.pie,\n {},\n 'piechartQuery',\n this.props.resultLayout.widgetOptions.properties.value\n )\n }\n }\n\n onClickSearch = () => {\n this.props.onClickSearch({\n savedSearchId: this.props.resultLayout.indexedSearches.pie,\n sonraiSearchName: this.props.options.sonraiSearches.pie,\n searchTitle: this.props.title,\n })\n }\n\n getSearchName = () => {\n return hasSonraiSearch(this.props.options, 'pie')\n ? this.props.options.sonraiSearches.pie\n : this.props.savedSearches.getIn([\n this.props.resultLayout.indexedSearches.pie,\n 'name',\n ])\n }\n\n onClickPoint = (params, data) => {\n const clickedLabel = params.labels[params.index]\n const sliceRepresentsNull = clickedLabel === 'null' || clickedLabel === '-'\n const targetSliceValue = sliceRepresentsNull ? null : clickedLabel\n const property = this.props.resultLayout.widgetOptions.properties.value\n\n let filter\n\n if (hasSonraiSearch(this.props.options, 'pie')) {\n filter = {\n label: data.property,\n value: targetSliceValue,\n }\n } else {\n filter = {\n label: property,\n value: targetSliceValue,\n }\n }\n\n this.props.onClickSearch({\n savedSearchId: this.props.resultLayout.indexedSearches.pie,\n sonraiSearchName: this.props.options.sonraiSearches.pie,\n searchTitle: this.props.title,\n savedSearchFilterCardId: getSearchCard(this.props.resultLayout, 'pie').id,\n filter: filter,\n })\n }\n\n getData = data => {\n if (hasSonraiSearch(this.props.options, 'pie')) {\n const items = getSonraiSearchData(data).items\n const flattened = flattenData(items)\n const property = this.props.resultLayout.widgetOptions.properties.value\n const counts = {}\n flattened.forEach(\n item => (counts[item[property]] = 1 + (counts[item[property]] || 0))\n )\n\n const countKeys = _.orderBy(\n Object.keys(counts),\n key => counts[key],\n 'desc'\n )\n return {\n data: { flattened, property, counts },\n labels: countKeys,\n slices: countKeys.map(key => counts[key]),\n }\n } else {\n const fields = getFields(\n this.props.savedSearches,\n this.props.resultLayout.indexedSearches.pie\n ).toJS()\n const searchCard = getSearchCard(this.props.resultLayout, 'pie')\n const selection = getSelection(fields, searchCard, 'group')\n const savedSearchResults = _.get(data, selection) || []\n\n const labels = []\n const slices = []\n\n !_.isEmpty(this.props.resultLayout.widgetOptions.properties.value) &&\n _.orderBy(savedSearchResults, item => item.count, 'desc').map(item => {\n const label =\n item.key[this.props.resultLayout.widgetOptions.properties.value]\n labels.push(label === null ? 'null' : label)\n slices.push(item.count)\n })\n\n return {\n data,\n labels,\n slices,\n }\n }\n }\n\n getSearchId = () => {\n const { options, sonraiSearches, resultLayout } = this.props\n let searchObj = Map({ uiSearch: null, advancedSearch: null })\n if (hasSonraiSearch(options, 'pie')) {\n const searches = getSearchIdForSonraiSearches(options, sonraiSearches)\n searchObj = searchObj.set('advancedSearch', searches)\n } else {\n searchObj = searchObj.set('uiSearch', resultLayout.indexedSearches.pie)\n }\n return searchObj\n }\n\n render() {\n if (this.props.data === undefined) {\n const searchId = this.getSearchId()\n const queryConfig = this.getQuery()\n if (!queryConfig.gqlStatement) {\n return (\n \n )\n }\n\n const filtered = queryHasPivotFilter(queryConfig.gqlStatement)\n return (\n \n {({ error, data, refetch, networkStatus }) => {\n const results = this.getData(data)\n return (\n \n {this.props.title}\n \n }\n filtered={filtered}\n error={error}\n containerStyle={{ padding: 0 }}\n description={_.get(this.props.resultLayout, [\n 'widgetOptions',\n 'description',\n ])}\n >\n {networkStatus !== 1 && !error && (\n \n \n this.onClickPoint(params, results.data)\n }\n colors={results.colors}\n yValues={results.slices}\n yLabels={results.labels}\n type=\"donut\"\n />\n \n )}\n \n \n {this.props.title}\n \n \n \n )\n }}\n \n )\n } else {\n return (\n \n \n \n \n\n \n {this.props.title}\n \n \n )\n }\n }\n}\n\nPieChartWidget.propTypes = {\n allowDelete: PropTypes.bool,\n allowUpdate: PropTypes.bool,\n data: PropTypes.shape({\n colors: PropTypes.arrayOf(PropTypes.string),\n slices: PropTypes.array,\n labels: PropTypes.array,\n }),\n disableToolbar: PropTypes.bool,\n noAnimate: PropTypes.bool,\n title: PropTypes.string.isRequired,\n onClickSearch: PropTypes.func,\n onRemove: PropTypes.func,\n toggleStatic: PropTypes.func,\n savedSearches: ImmutablePropTypes.map.isRequired,\n static: PropTypes.bool,\n options: PropTypes.object,\n onChartReady: PropTypes.func,\n resultLayout: PropTypes.object,\n getQueryBuilder: PropTypes.func,\n onEdit: PropTypes.func,\n sonraiSearches: ImmutablePropTypes.iterable,\n widget: PropTypes.object,\n}\n\nexport default PieChartWidget\n","import React from 'react'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport PropTypes from 'prop-types'\nimport { Input } from 'reactstrap'\nimport { Query } from 'react-apollo'\nimport gql from 'graphql-tag'\nimport SelectBar from 'components/SelectBar'\nimport _ from 'lodash'\nimport { List } from 'immutable'\nimport FormLabel from 'components/FormLabel'\nimport { QueryBuilder } from 'query-builder'\nimport {\n getFields,\n getTableSavedSearchQuery,\n getSelection,\n getSonraiSearchQueryString,\n getSonraiSearchData,\n} from 'query-builder'\nimport CombinedSearches from 'components/CombinedSearches'\nimport { flattenData } from 'utils/sonraiUtils'\nimport PieChartWidget from 'components/PieChartWidget'\nimport PreviewContainer from './PreviewContainer'\n\nconst styles = {\n wrapperWithPreview: {\n display: 'grid',\n gridTemplateColumns: '35% 1fr',\n gridColumnGap: '2em',\n },\n}\n\nclass PieChartWidgetConfig extends React.Component {\n componentDidUpdate() {\n this.updateValidity()\n }\n\n updateValidity = () => {\n const hasSonraiSearch = !!this.props.widgetSonraiSearches.pie\n const hasSavedSearch =\n !!this.props.widgetSavedSearches.pie && !!this.props.widgetSearchCards.pie\n const hasTitle = this.props.widgetTitle\n\n const hasDisplayField = !!this.props.widgetOptions.properties\n\n const valid =\n (hasSonraiSearch || hasSavedSearch) && hasTitle && hasDisplayField\n\n this.props.setValidity(valid)\n }\n\n setTitle = e => {\n this.props.setWidgetTitle(e.target.value)\n }\n\n setDescription = e => {\n const string = e.target.value\n\n this.props.setWidgetOptions({\n description: string,\n })\n }\n\n setSavedSearch = selectedOption => {\n this.props.setWidgetSavedSearch(\n selectedOption ? selectedOption.value : null,\n 'pie'\n )\n\n this.setListField(null)\n }\n\n setSearchField = selectedOption => {\n this.props.setWidgetSearchField(\n selectedOption ? selectedOption.value : null,\n 'pie'\n )\n\n this.setListField(null)\n }\n\n setSonraiSearch = selectedValue => {\n this.props.setWidgetSonraiSearch(\n selectedValue ? selectedValue.value : undefined,\n 'pie'\n )\n\n this.setListField(null)\n }\n\n setListField = selectedOption => {\n this.props.setWidgetOptions({\n ...this.props.widgetOptions,\n properties: selectedOption,\n })\n }\n\n getQueryBuilder = query => {\n return new QueryBuilder({\n query,\n types: this.props.queryTypes,\n })\n }\n\n getSearchQuery = () => {\n if (this.props.widgetSonraiSearches.pie) {\n return getSonraiSearchQueryString(this.props.widgetSonraiSearches.pie)\n } else if (\n this.props.widgetSavedSearches['pie'] &&\n !!this.getSearchCard()\n ) {\n const queryConfig = getTableSavedSearchQuery(\n this.getQueryBuilder,\n this.props.savedSearches,\n this.props.widgetSavedSearches['pie'],\n { limit: 1 }\n )\n\n return queryConfig.gqlStatement\n }\n }\n\n getSearchCard = () => {\n return this.props.widgetSearchCards['pie']\n }\n\n getSampleItemFromData = data => {\n if (this.props.widgetSonraiSearches.pie) {\n const results = getSonraiSearchData(data).items\n return results ? flattenData(results)[0] : {}\n } else {\n const fields = getFields(\n this.props.savedSearches,\n this.props.widgetSavedSearches['pie']\n ).toJS()\n\n const path = getSelection(fields, this.getSearchCard(), 'items')\n\n const results = _.get(data, path) || []\n return results.length > 0 ? results[0] : {}\n }\n }\n\n renderPropertyDropDown = () => {\n const query = this.getSearchQuery()\n\n if (!query) {\n return (\n \n )\n }\n\n return (\n \n {({ data, networkStatus }) => {\n if (networkStatus === 1) {\n return (\n \n )\n }\n\n if (!_.isEmpty(data)) {\n const item = this.getSampleItemFromData(data)\n\n if (!_.isEmpty(item)) {\n const fields = Object.keys(item).map(val => ({\n label: val,\n value: val,\n }))\n\n return (\n \n )\n }\n }\n\n return (\n \n )\n }}\n \n )\n }\n\n render() {\n const sonraiSearchName = this.props.widgetSonraiSearches['pie']\n const savedSearchId = this.props.widgetSavedSearches['pie']\n const savedSearchField = this.props.widgetSearchCards['pie']\n const searchFieldsList = this.props.searchCards.get(\n this.props.widgetSavedSearches['pie'],\n List()\n )\n\n return (\n \n )\n }\n}\n\nPieChartWidgetConfig.propTypes = {\n getQueryBuilder: PropTypes.func,\n previewWidget: PropTypes.bool,\n queryTypes: ImmutablePropTypes.map,\n savedSearches: ImmutablePropTypes.mapOf(\n ImmutablePropTypes.contains({\n sid: PropTypes.string,\n name: PropTypes.string,\n })\n ),\n savedSonraiSearches: ImmutablePropTypes.list,\n searchCards: ImmutablePropTypes.mapOf(\n ImmutablePropTypes.contains({\n id: PropTypes.string,\n name: PropTypes.string,\n })\n ),\n setWidgetSavedSearch: PropTypes.func,\n setWidgetSearchField: PropTypes.func,\n setWidgetSonraiSearch: PropTypes.func,\n setWidgetOptions: PropTypes.func,\n setWidgetTitle: PropTypes.func,\n setValidity: PropTypes.func,\n widgetOptions: PropTypes.shape({\n items: PropTypes.array,\n properties: PropTypes.array,\n description: PropTypes.string,\n }).isRequired,\n widgetSavedSearches: PropTypes.objectOf(PropTypes.array),\n widgetSearchCards: PropTypes.objectOf(PropTypes.object),\n widgetSonraiSearches: PropTypes.object,\n widgetTitle: PropTypes.string,\n}\n\nexport default PieChartWidgetConfig\n","/**\n *\n * BarChartWidget\n *\n */\n\nimport React from 'react'\nimport PropTypes from 'prop-types'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport _ from 'lodash'\nimport { Query } from 'react-apollo'\nimport gql from 'graphql-tag'\n\nimport TextLink from 'components/TextLink'\nimport Chart from 'components/Chart'\nimport Card, { CardBody, BottomTitle } from 'components/Card'\nimport WidgetCard from 'components/WidgetCard'\nimport { Map } from 'immutable'\nimport {\n getFields,\n getGroupedSavedSearchQuery,\n getSearchCard,\n hasSonraiSearch,\n getSelection,\n getSonraiSearchQuery,\n getSonraiSearchData,\n} from 'query-builder'\nimport { queryHasPivotFilter } from 'query-builder'\nimport { flattenData, getSearchIdForSonraiSearches } from 'utils/sonraiUtils'\n\nclass BarChartWidget extends React.Component {\n getQuery = () => {\n if (hasSonraiSearch(this.props.options, 'bar')) {\n return getSonraiSearchQuery(this.props.options.sonraiSearches.bar)\n } else {\n return getGroupedSavedSearchQuery(\n this.props.getQueryBuilder,\n this.props.savedSearches,\n this.props.resultLayout.indexedSearches.bar,\n {},\n 'barchartQuery',\n this.props.resultLayout.widgetOptions.properties.value\n )\n }\n }\n\n onClickSearch = () => {\n this.props.onClickSearch({\n savedSearchId: this.props.resultLayout.indexedSearches.bar,\n sonraiSearchName: this.props.options.sonraiSearches.bar,\n searchTitle: this.props.title,\n })\n }\n\n getSearchName = () => {\n return hasSonraiSearch(this.props.options, 'bar')\n ? this.props.options.sonraiSearches.bar\n : this.props.savedSearches.getIn([\n this.props.resultLayout.indexedSearches.bar,\n 'name',\n ])\n }\n\n onClickPoint = params => {\n const clickedLabel = params.labels[params.index]\n const sliceRepresentsNull = clickedLabel === 'null' || clickedLabel === '-'\n const targetSliceValue = sliceRepresentsNull ? null : clickedLabel\n const property = this.props.resultLayout.widgetOptions.properties.value\n\n const filter = {\n label: property,\n value: targetSliceValue,\n }\n\n this.props.onClickSearch({\n savedSearchId: this.props.resultLayout.indexedSearches.bar,\n sonraiSearchName: this.props.options.sonraiSearches.bar,\n searchTitle: this.props.title,\n savedSearchFilterCardId: getSearchCard(this.props.resultLayout, 'bar').id,\n filter: filter,\n })\n }\n\n getData = data => {\n if (hasSonraiSearch(this.props.options, 'bar')) {\n const items = getSonraiSearchData(data).items\n const flattened = flattenData(items)\n\n const property = this.props.resultLayout.widgetOptions.properties.value\n const counts = {}\n flattened.forEach(\n item => (counts[item[property]] = 1 + (counts[item[property]] || 0))\n )\n\n const countKeys = Object.keys(counts)\n return {\n x: countKeys,\n y: countKeys.map(key => counts[key]),\n }\n } else {\n const fields = getFields(\n this.props.savedSearches,\n this.props.resultLayout.indexedSearches.bar\n ).toJS()\n const searchCard = getSearchCard(this.props.resultLayout, 'bar')\n const selection = getSelection(fields, searchCard, 'group')\n const savedSearchResults = _.get(data, selection) || []\n\n const labels = []\n const bars = []\n !_.isEmpty(this.props.resultLayout.widgetOptions.properties.value) &&\n savedSearchResults.map(item => {\n const label =\n item.key[this.props.resultLayout.widgetOptions.properties.value]\n labels.push(label === null ? 'null' : label)\n bars.push(item.count)\n })\n\n return {\n x: labels,\n y: bars,\n }\n }\n }\n\n getSearchId = () => {\n const { options, sonraiSearches, resultLayout } = this.props\n let searchObj = Map({ uiSearch: null, advancedSearch: null })\n if (hasSonraiSearch(options, 'bar')) {\n const searches = getSearchIdForSonraiSearches(options, sonraiSearches)\n searchObj = searchObj.set('advancedSearch', searches)\n } else {\n searchObj = searchObj.set('uiSearch', resultLayout.indexedSearches.bar)\n }\n return searchObj\n }\n\n render() {\n if (this.props.data === undefined) {\n const searchId = this.getSearchId()\n const queryConfig = this.getQuery()\n if (!queryConfig.gqlStatement) {\n return (\n \n )\n }\n\n const filtered = queryHasPivotFilter(queryConfig.gqlStatement)\n\n return (\n \n {({ error, data, refetch, networkStatus }) => {\n const results = this.getData(data)\n\n return (\n \n {this.props.title}\n \n }\n filtered={filtered}\n error={error}\n containerStyle={{ padding: 0 }}\n description={_.get(this.props.resultLayout, [\n 'widgetOptions',\n 'description',\n ])}\n >\n \n this.onClickPoint(params, data)}\n yValues={results.y}\n yLabels={results.x}\n type=\"bar\"\n yTitle={this.props.title}\n />\n \n \n \n {this.props.title}\n \n \n \n )\n }}\n \n )\n } else {\n return (\n \n \n \n \n\n \n {this.props.title}\n \n \n )\n }\n }\n}\n\nBarChartWidget.propTypes = {\n allowDelete: PropTypes.bool,\n allowUpdate: PropTypes.bool,\n disableToolbar: PropTypes.bool,\n title: PropTypes.string.isRequired,\n onRemove: PropTypes.func,\n toggleStatic: PropTypes.func,\n savedSearches: ImmutablePropTypes.map.isRequired,\n static: PropTypes.bool,\n noAnimate: PropTypes.bool,\n options: PropTypes.object,\n onClickSearch: PropTypes.func.isRequired,\n onChartReady: PropTypes.func,\n resultLayout: PropTypes.object,\n getQueryBuilder: PropTypes.func,\n data: PropTypes.shape({\n x: PropTypes.array,\n y: PropTypes.array,\n }),\n onEdit: PropTypes.func,\n sonraiSearches: ImmutablePropTypes.iterable,\n widget: PropTypes.object,\n}\n\nexport default BarChartWidget\n","import React from 'react'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport PropTypes from 'prop-types'\nimport { Input } from 'reactstrap'\nimport { Query } from 'react-apollo'\nimport gql from 'graphql-tag'\nimport SelectBar from 'components/SelectBar'\nimport _ from 'lodash'\nimport { List } from 'immutable'\nimport FormLabel from 'components/FormLabel'\nimport { QueryBuilder } from 'query-builder'\nimport {\n getFields,\n getTableSavedSearchQuery,\n getSelection,\n getSonraiSearchQueryString,\n getSonraiSearchData,\n} from 'query-builder'\nimport CombinedSearches from 'components/CombinedSearches'\nimport { flattenData } from 'utils/sonraiUtils'\nimport BarChartWidget from 'components/BarChartWidget'\nimport PreviewContainer from './PreviewContainer'\n\nconst styles = {\n wrapperWithPreview: {\n display: 'grid',\n gridTemplateColumns: '45% 1fr',\n gridColumnGap: '2em',\n },\n}\nclass BarChartWidgetConfig extends React.Component {\n componentDidUpdate() {\n this.updateValidity()\n }\n\n setTitle = e => {\n this.props.setWidgetTitle(e.target.value)\n }\n\n setDescription = e => {\n const string = e.target.value\n\n this.props.setWidgetOptions({\n description: string,\n })\n }\n\n selectSavedSearch = selectedOption => {\n this.props.setWidgetSavedSearch(\n selectedOption ? selectedOption.value : null,\n 'bar'\n )\n\n this.setListField(null)\n }\n\n selectSearchField = selectedOption => {\n this.props.setWidgetSearchField(\n selectedOption ? selectedOption.value : null,\n 'bar'\n )\n\n this.setListField(null)\n }\n\n setSonraiSearch = selectedValue => {\n this.props.setWidgetSonraiSearch(\n selectedValue ? selectedValue.value : undefined,\n 'bar'\n )\n\n this.setListField(null)\n }\n\n setListField = selectedOption => {\n this.props.setWidgetOptions({\n ...this.props.widgetOptions,\n properties: selectedOption,\n })\n }\n\n getQueryBuilder = query => {\n return new QueryBuilder({\n query,\n types: this.props.queryTypes,\n })\n }\n\n getSearchQuery = () => {\n if (this.props.widgetSonraiSearches.bar) {\n return getSonraiSearchQueryString(this.props.widgetSonraiSearches.bar)\n } else if (\n this.props.widgetSavedSearches['bar'] &&\n !!this.getSearchCard()\n ) {\n const queryConfig = getTableSavedSearchQuery(\n this.getQueryBuilder,\n this.props.savedSearches,\n this.props.widgetSavedSearches['bar'],\n { limit: 1 }\n )\n\n return queryConfig.gqlStatement\n }\n }\n\n getSearchCard = () => {\n return this.props.widgetSearchCards['bar']\n }\n\n getSampleItemFromData = data => {\n if (this.props.widgetSonraiSearches.bar) {\n const results = getSonraiSearchData(data).items\n return results ? flattenData(results)[0] : {}\n } else {\n const fields = getFields(\n this.props.savedSearches,\n this.props.widgetSavedSearches['bar']\n ).toJS()\n\n const path = getSelection(fields, this.getSearchCard(), 'items')\n\n const results = _.get(data, path) || []\n return results.length > 0 ? results[0] : {}\n }\n }\n\n updateValidity = () => {\n const hasSonraiSearch = !!this.props.widgetSonraiSearches.bar\n const hasSavedSearch =\n !!this.props.widgetSavedSearches.bar && !!this.getSearchCard()\n const hasTitle = this.props.widgetTitle\n const hasDisplayField = !!this.props.widgetOptions.properties\n\n const valid =\n (hasSonraiSearch || hasSavedSearch) && hasTitle && hasDisplayField\n this.props.setValidity(valid)\n }\n\n renderPropertyDropDown = () => {\n const query = this.getSearchQuery()\n if (!query) {\n return (\n \n )\n }\n\n return (\n \n {({ data, networkStatus }) => {\n if (networkStatus === 1) {\n return (\n \n )\n }\n\n if (!_.isEmpty(data)) {\n const item = this.getSampleItemFromData(data)\n\n if (!_.isEmpty(item)) {\n const fields = Object.keys(item).map(val => ({\n label: val,\n value: val,\n }))\n\n return (\n \n )\n }\n }\n\n return (\n \n )\n }}\n \n )\n }\n\n render() {\n const sonraiSearchName = this.props.widgetSonraiSearches['bar']\n const savedSearchId = this.props.widgetSavedSearches['bar']\n const savedSearchField = this.props.widgetSearchCards['bar']\n const searchFieldsList = this.props.searchCards.get(\n this.props.widgetSavedSearches['bar'],\n List()\n )\n\n return (\n \n )\n }\n}\n\nBarChartWidgetConfig.propTypes = {\n getQueryBuilder: PropTypes.func,\n queryTypes: ImmutablePropTypes.map,\n savedSearches: ImmutablePropTypes.mapOf(\n ImmutablePropTypes.contains({\n sid: PropTypes.string,\n name: PropTypes.string,\n })\n ),\n savedSonraiSearches: ImmutablePropTypes.list,\n searchCards: ImmutablePropTypes.mapOf(\n ImmutablePropTypes.contains({\n id: PropTypes.string,\n name: PropTypes.string,\n })\n ),\n setWidgetSavedSearch: PropTypes.func,\n setWidgetSearchField: PropTypes.func,\n setWidgetSonraiSearch: PropTypes.func,\n setWidgetOptions: PropTypes.func,\n setWidgetTitle: PropTypes.func,\n setValidity: PropTypes.func,\n previewWidget: PropTypes.bool,\n widgetOptions: PropTypes.shape({\n items: PropTypes.array,\n properties: PropTypes.array,\n description: PropTypes.string,\n }).isRequired,\n widgetSavedSearches: PropTypes.objectOf(PropTypes.array),\n widgetSearchCards: PropTypes.objectOf(PropTypes.object),\n widgetSonraiSearches: PropTypes.object,\n widgetTitle: PropTypes.string,\n}\n\nexport default BarChartWidgetConfig\n","import React from 'react'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport PropTypes from 'prop-types'\nimport { Input } from 'reactstrap'\nimport { Map } from 'immutable'\n\nimport { ARC_MAP_SEARCH_NAME, SUPPORT_EMAIL } from 'appConstants'\nimport CombinedSearches from 'components/CombinedSearches'\nimport FormLabel from 'components/FormLabel'\n\nexport class AdvMapWidgetConfig extends React.Component {\n componentDidUpdate() {\n this.updateValidity()\n }\n\n updateValidity = () => {\n const hasSonraiSearch = !!this.props.widgetSonraiSearches.advmapsearch\n const hasSavedSearch = !!this.props.widgetSavedSearches.advmapsearch\n\n const hasTitle = this.props.widgetTitle\n\n const valid = (hasSonraiSearch || hasSavedSearch) && hasTitle\n this.props.setValidity(valid)\n }\n\n setTitle = e => {\n this.props.setWidgetTitle(e.target.value)\n }\n\n setDescription = e => {\n const string = e.target.value\n\n this.props.setWidgetOptions({\n description: string,\n })\n }\n\n setSonraiSearch = selectedValue => {\n this.props.setWidgetSonraiSearch(\n selectedValue ? selectedValue.value : undefined,\n 'advmapsearch'\n )\n }\n\n selectSavedSearch = selectedOption => {\n this.props.setWidgetSavedSearch(\n selectedOption ? selectedOption.value : null,\n 'advmapsearch'\n )\n }\n\n selectSearchField = selectedOption => {\n this.props.setWidgetSearchField(\n selectedOption ? selectedOption.value : null,\n 'advmapsearch'\n )\n }\n\n render() {\n const sonraiSearchName = this.props.widgetSonraiSearches['advmapsearch']\n const savedSearchId = this.props.widgetSavedSearches['advmapsearch']\n\n const validSavedSearches = this.props.savedSearches.filter(search => {\n const fields = search.getIn(['query', 'fields'], Map())\n const actions = fields.filter(field => {\n return field.getIn(['definition', 'name'], '') === 'Actions'\n })\n\n const locations = fields.filter(field => {\n return (\n field.getIn(['definition', 'name'], '') === 'performedAt' &&\n actions.has(field.get('parentId'))\n )\n })\n\n return !actions.isEmpty() && !locations.isEmpty()\n })\n\n return (\n \n
\n
\n\n
\n
\n\n
\n
\n Note: The search must contain Actions attached to Locations. For help\n crafting these searches, please contact{' '}\n support\n
\n
\n search.get('name').includes(ARC_MAP_SEARCH_NAME)\n )}\n setSonraiSearch={this.setSonraiSearch}\n selectedSonraiValue={sonraiSearchName}\n savedSearches={validSavedSearches}\n selectedSearchId={savedSearchId}\n selectSavedSearch={this.selectSavedSearch}\n savedSearchDisabled={!!sonraiSearchName}\n />\n \n )\n }\n}\n\nAdvMapWidgetConfig.propTypes = {\n savedSearches: ImmutablePropTypes.iterable.isRequired,\n savedSonraiSearches: ImmutablePropTypes.list.isRequired,\n setWidgetSavedSearch: PropTypes.func.isRequired,\n setWidgetSearchField: PropTypes.func.isRequired,\n setWidgetSonraiSearch: PropTypes.func,\n setWidgetOptions: PropTypes.func,\n setWidgetTitle: PropTypes.func,\n setValidity: PropTypes.func,\n widgetOptions: PropTypes.shape({\n items: PropTypes.array,\n properties: PropTypes.array,\n description: PropTypes.string,\n }).isRequired,\n widgetSavedSearches: PropTypes.object,\n widgetSonraiSearches: PropTypes.object,\n widgetTitle: PropTypes.string,\n}\n\nexport default AdvMapWidgetConfig\n","/*\n * AlertWidget Messages\n *\n * This contains all the text for the AlertWidget component.\n */\nimport { defineMessages } from 'react-intl'\n\nexport default defineMessages({\n header: {\n id: 'app.components.AlertWidget.header',\n defaultMessage: 'This is the AlertWidget component !',\n },\n findings: {\n id: 'app.components.AlertWidget.findings',\n defaultMessage: 'Findings',\n },\n noData: {\n id: 'app.components.AlertWidget.noData',\n defaultMessage: 'No Data to show',\n },\n})\n","import React, { Component } from 'react'\nimport Icon from 'components/Icon'\nimport PropTypes from 'prop-types'\nimport { getAlertColors } from 'utils/widgetUtils'\nimport messages from './messages'\nimport { FormattedMessage } from 'react-intl'\nimport themeable, { themeShape } from 'containers/ThemeManager/Themeable'\nimport SonraiCountDisplay from 'components/SonraiCountDisplay'\n\nclass SingleAlert extends Component {\n constructor(props) {\n super(props)\n\n this.styles = {\n alertContainer: {\n minWidth: '100%',\n border: '1px solid whitesmoke',\n display: 'flex',\n borderRadius: 10,\n minHeight: '4.25rem',\n margin: '0.25rem 0rem',\n },\n numberContainer: {\n width: '25%',\n height: '100%',\n minHeight: '4.25rem',\n minWidth: '5rem',\n padding: '0.25rem 1rem',\n display: 'flex',\n alignItems: 'center',\n borderBottomLeftRadius: 10,\n borderTopLeftRadius: 10,\n flexDirection: 'column',\n backgroundColor: getAlertColors(props.level, props.theme)\n .backgroundColor,\n },\n alertCount: {\n fontSize: '1.85rem',\n fontWeight: '500',\n color: getAlertColors(props.level, props.theme).color,\n },\n findings: {\n fontSize: '0.8rem',\n fontWeight: '400',\n color: getAlertColors(props.level, props.theme).color,\n lineHeight: 0,\n },\n secondaryContainer: {\n width: '100%',\n display: 'flex',\n flexDirection: 'column',\n justifyContent: 'space-between',\n minWidth: '270px',\n },\n policyName: {\n alignSelf: 'flex-start',\n overflow: 'hidden',\n padding: '0.8rem 0rem 0rem 0.7rem',\n },\n policyText: {\n fontSize: '0.8rem',\n margin: 0,\n width: '235px',\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n },\n iconCont: {\n padding: '0rem 0.7rem 0.8rem 0.7rem',\n display: 'flex',\n alignSelf: 'flex-start',\n alignItems: 'center',\n },\n time: { fontSize: '0.65rem', marginLeft: '0.5rem' },\n }\n }\n render() {\n return (\n \n
this.props.onClickAlert(this.props.id)}\n style={this.styles.numberContainer}\n >\n
\n \n
\n
\n \n
\n
\n
\n
\n
\n
\n
{this.props.time}
\n
\n
\n
\n )\n }\n}\n\nSingleAlert.propTypes = {\n id: PropTypes.string.isRequired,\n onClickAlert: PropTypes.func.isRequired,\n style: PropTypes.object.isRequired,\n count: PropTypes.number.isRequired,\n name: PropTypes.string.isRequired,\n level: PropTypes.number.isRequired,\n time: PropTypes.string.isRequired,\n theme: themeShape,\n}\n\nexport default themeable(SingleAlert)\n","/**\n *\n * AlertWidget\n *\n */\n\nimport React from 'react'\nimport PropTypes from 'prop-types'\nimport Radium from 'radium'\nimport { Query } from 'react-apollo'\nimport { exists } from 'utils/sonraiUtils'\nimport Card, { TopTitle, CardBody } from 'components/Card'\nimport moment from 'moment'\nimport _ from 'lodash'\nimport gql from 'graphql-tag'\nimport WidgetCard from 'components/WidgetCard'\nimport { ALERT_LEVELS, CONTROL_FRAMEWORKS } from '../../appConstants'\nimport SingleAlert from './SingleAlert'\nimport messages from './messages'\nimport { FormattedMessage } from 'react-intl'\nimport { Map } from 'immutable'\n\nconst styles = {\n header: { fontSize: '22px', fontWeight: '300' },\n alertsMainContainer: {\n display: 'flex',\n flexDirection: 'column',\n width: '100%',\n height: '100%',\n maxHeight: '5rem',\n margin: '0.5rem 0rem',\n },\n}\n\nclass AlertWidget extends React.Component {\n setLevel = policy => {\n switch (policy.alertingLevel) {\n case ALERT_LEVELS.INFO:\n return 1\n case ALERT_LEVELS.WARN:\n return 2\n case ALERT_LEVELS.ERROR:\n return 3\n default:\n return 3\n }\n }\n\n getData = data => {\n if (exists(data)) {\n if (exists(data.PolicyEvalLogs)) {\n const logs = data.PolicyEvalLogs.items.filter(item => !item.pass)\n if (!_.isEmpty(logs)) {\n return logs\n .map(policy => ({\n id: policy.policyId,\n key: policy.id,\n // default title just put an empty tick mark\n name: (policy.controlPolicy || { title: '-' }).title,\n count: policy.count || '-', // This is here until the count: null bug is fixed :)\n time: moment.unix(policy.time).fromNow(),\n level: this.setLevel(policy),\n }))\n .sort((a, b) => b.level - a.level)\n .sort((a, b) => new Date(b.time - new Date(a.time)))\n } else {\n return []\n }\n }\n } else []\n }\n\n renderAlerts = alerts => {\n if (exists(alerts)) {\n if (_.isEmpty(alerts)) {\n return (\n \n {' '}\n {' '}\n
\n )\n } else {\n return (\n \n {alerts.map(alert => (\n \n ))}\n
\n )\n }\n }\n }\n\n getQuery = () => {\n const { widgetOptions } = this.props.resultLayout\n if (exists(widgetOptions)) {\n if (widgetOptions.alert) {\n if (\n widgetOptions.alert.value !==\n CONTROL_FRAMEWORKS.ALL_CONTROL_FRAMEWORKS\n ) {\n const query = gql`\n query getAlertsByControlFramework {\n PolicyEvalLogs(where: { \n controlFrameworkId: \"${widgetOptions.alert.value}\", \n collapse: true\n }) {\n count\n items {\n policyId\n controlPolicy {\n title\n srn\n }\n pass\n time\n alertingLevel\n count\n srnList\n id\n }\n }\n }\n `\n return query\n } else {\n return gql`\n query getAllAlerts {\n PolicyEvalLogs(where: { collapse: true }) {\n count\n items {\n policyId\n controlPolicy {\n title\n srn\n }\n pass\n time\n alertingLevel\n count\n srnList\n id\n }\n }\n }\n `\n }\n }\n }\n }\n\n renderHeader = () => {\n return (\n \n )\n }\n\n getSearchId = () => {\n //Because it doesn't support saved or sonrai searches - just control frameworks\n return Map({\n uiSearch: null,\n advancedSearch: null,\n })\n }\n render() {\n if (this.props.data === undefined) {\n const searchId = this.getSearchId()\n const query = this.getQuery()\n if (!query) {\n return (\n \n )\n }\n\n return (\n \n {({ error, loading, data, networkStatus, refetch }) => {\n const alerts = loading ? null : this.getData(data)\n\n return (\n \n {this.renderHeader()}\n \n {this.renderAlerts(alerts)}\n \n \n )\n }}\n \n )\n } else {\n return (\n \n {this.props.title && {this.renderHeader()}}\n \n {this.renderAlerts(this.props.data)}\n \n \n )\n }\n }\n}\n\nAlertWidget = Radium(AlertWidget) // eslint-disable-line\n\nAlertWidget.propTypes = {\n title: PropTypes.string.isRequired,\n onClickAlert: PropTypes.func.isRequired,\n data: PropTypes.object,\n resultLayout: PropTypes.object,\n allowDelete: PropTypes.bool,\n allowUpdate: PropTypes.bool,\n onRemove: PropTypes.func,\n onEdit: PropTypes.func,\n static: PropTypes.bool,\n disableToolbar: PropTypes.bool,\n toggleStatic: PropTypes.func,\n widget: PropTypes.object,\n}\n\nexport default AlertWidget\n","import React from 'react'\nimport PropTypes from 'prop-types'\nimport { Input } from 'reactstrap'\nimport { Map } from 'immutable'\nimport moment from 'moment'\nimport _ from 'lodash'\n\nimport AlertWidget from 'components/AlertWidget'\nimport FormLabel from 'components/FormLabel'\nimport SelectBar from 'components/SelectBar'\nimport { guidGen } from 'utils/sonraiUtils'\nimport { CONTROL_FRAMEWORKS } from 'appConstants'\nimport PreviewContainer from './PreviewContainer'\n\nconst styles = {\n wrapperWithPreview: {\n display: 'grid',\n gridTemplateColumns: '45% 1fr',\n gridColumnGap: '2em',\n },\n alertPreview: { minWidth: '225px', height: '350px', maxHeight: '350px' },\n}\n\nconst placeholderData = [\n {\n id: guidGen(),\n key: guidGen(),\n name: 'Placeholder',\n count: 4,\n time: moment().fromNow(),\n level: Math.floor(Math.random() * 3) + 1,\n },\n {\n id: guidGen(),\n key: guidGen(),\n name: 'Placeholder',\n count: Math.floor(Math.random() * 10) + 1,\n time: moment().fromNow(),\n level: Math.floor(Math.random() * 3) + 1,\n },\n {\n id: guidGen(),\n key: guidGen(),\n name: 'Placeholder',\n count: Math.floor(Math.random() * 10) + 1,\n time: moment().fromNow(),\n level: Math.floor(Math.random() * 3) + 1,\n },\n].sort((a, b) => b.level - a.level)\n\nclass AlertWidgetConfig extends React.Component {\n componentDidUpdate() {\n this.updateValidity()\n }\n\n updateValidity = () => {\n const hasControlFrameWork = !!this.props.widgetOptions.alert\n const hasTitle = this.props.widgetTitle\n\n const valid = hasControlFrameWork && hasTitle\n this.props.setValidity(valid)\n }\n\n handleSelectControlFrameWork = val => {\n this.props.setWidgetOptions({\n alert: val,\n })\n\n this.handleSuggestedTitle(this.getSuggestedTitle(val))\n }\n\n setTitle = e => {\n this.props.setWidgetTitle(e.target.value)\n }\n\n setDescription = e => {\n this.props.setWidgetOptions({\n description: e.target.value,\n })\n }\n\n handleSuggestedTitle = title => this.props.setWidgetTitle(title)\n\n getSuggestedTitle = selection => {\n if (selection !== null) {\n const message =\n selection.label === CONTROL_FRAMEWORKS.ALL_CONTROL_FRAMEWORKS\n ? 'All Alerts'\n : `Alerts for ${selection.label}`\n return message\n }\n }\n\n render() {\n const enabledControlFrameworks = this.props.controlGroupOptions\n .filter(cf => cf.get('enabled'))\n .toList()\n .map(cf =>\n Map({\n value: cf.get('srn'),\n label: cf.get('title'),\n })\n )\n .sortBy(cf => cf.get('label').toLowerCase())\n\n const options =\n enabledControlFrameworks.size > 1\n ? [\n {\n value: CONTROL_FRAMEWORKS.ALL_CONTROL_FRAMEWORKS,\n label: 'All Control Frameworks',\n },\n ...enabledControlFrameworks.toJS(),\n ]\n : enabledControlFrameworks.toJS()\n\n return (\n \n )\n }\n}\n\nAlertWidgetConfig.propTypes = {\n previewWidget: PropTypes.bool,\n setWidgetOptions: PropTypes.func,\n setWidgetTitle: PropTypes.func,\n setValidity: PropTypes.func,\n widgetOptions: PropTypes.shape({\n alert: PropTypes.string,\n description: PropTypes.string,\n }),\n\n widgetTitle: PropTypes.string,\n controlGroupOptions: PropTypes.object,\n}\n\nexport default AlertWidgetConfig\n","import React, { Component } from 'react'\nimport PropTypes from 'prop-types'\n\nexport default class WidgetModalErrorBoundary extends Component {\n constructor(props) {\n super(props)\n this.state = {\n hasError: false,\n errorMessage: '',\n errorSource: '',\n componentStack: '',\n errorStack: '',\n }\n\n this.styles = {\n errorWrapper: {\n display: 'flex',\n flexDirection: 'column',\n justifyContent: 'center',\n height: '20vh',\n color: '#666666',\n },\n }\n }\n\n componentDidCatch(error, info) {\n this.setState({\n hasError: true,\n errorMessage: error.message,\n errorStack: error.stack,\n componentStack: info.componentStack,\n errorSource: error.source ? error.source.body : '',\n })\n }\n\n logError = () => {\n // eslint-disable-next-line no-console\n console.log({ json: JSON.stringify(this.state), ...this.state })\n }\n\n render() {\n if (this.state.hasError) {\n return (\n \n
\n
\n Oops... An error occured while editing this widget\n
\n
\n {localStorage.getItem('sonraiDebugMode') && (\n
\n \n \n \n \n {this.state.errorMessage}\n
\n \n {this.state.errorSource}
\n {this.state.errorStack}
\n {this.state.componentStack}
\n \n \n )}\n
\n )\n }\n\n return this.props.children\n }\n}\nWidgetModalErrorBoundary.propTypes = {\n children: PropTypes.node.isRequired,\n}\n","import React from 'react'\nimport PropTypes from 'prop-types'\n\nimport Icon from 'components/Icon'\nimport Hoverable from 'components/Hoverable'\nimport themeable, { themeShape } from 'containers/ThemeManager/Themeable'\nimport IconLayer from 'components/IconLayer'\nimport Tooltip from 'components/Tooltip'\n\nexport const COMPOUND_ICONS = {\n BIG_STAT: 'bigstat',\n}\n\nclass WidgetType extends React.PureComponent {\n constructor(props) {\n super(props)\n\n this.styles = {\n pickerIcon: {\n display: 'flex',\n justifyContent: 'center',\n },\n pickerTitle: {\n gridArea: 'title',\n paddingTop: '1.5em',\n display: 'flex',\n justifyContent: 'center',\n },\n tooltip: {\n position: 'absolute',\n top: '3px',\n right: '3px',\n },\n wrapper: {\n borderRadius: '3px',\n padding: '1em',\n position: 'relative',\n height: '100%',\n },\n content: {\n display: 'grid',\n gridTemplateRows: '1fr auto',\n gridTemplateAreas: '\"icon\" \"title\"',\n justifyContent: 'center',\n height: '100%',\n },\n }\n }\n\n getIcon = hovered => {\n const commonIconProps = {\n color: hovered ? this.props.theme.primary : this.props.theme.neutralDark,\n style: { fontSize: '40px' },\n }\n\n if (this.props.icon) {\n return (\n \n )\n }\n\n if (this.props.compoundIcon) {\n switch (this.props.compoundIcon) {\n case COMPOUND_ICONS.BIG_STAT:\n return (\n \n \n \n \n \n )\n }\n }\n }\n\n render() {\n const { label, onClick, theme, tooltipContent } = this.props\n return (\n {\n return (\n \n
\n
\n {this.getIcon(hovered)}\n
\n
\n {label}\n
\n
\n {tooltipContent && (\n
\n }\n />\n
\n )}\n
\n )\n }}\n />\n )\n }\n}\n\nWidgetType.propTypes = {\n icon: PropTypes.string,\n compoundIcon: PropTypes.string,\n label: PropTypes.node,\n onClick: PropTypes.func,\n theme: themeShape,\n tooltipContent: PropTypes.node,\n transformIcon: PropTypes.string,\n}\n\nexport default themeable(WidgetType)\n","import React from 'react'\nimport PropTypes from 'prop-types'\n\nimport { WIDGET_TYPES } from 'appConstants'\nimport WidgetType, { COMPOUND_ICONS } from './WidgetType'\n\nconst styles = {\n pickerContainer: {\n display: 'grid',\n gridTemplateColumns: '1fr 1fr 1fr',\n gridColumnGap: '2em',\n gridRowGap: '2em',\n },\n}\n\nexport const WidgetTypePicker = ({ selectType }) => {\n return (\n \n
\n Widgets can display the results of saved searches in multiple different\n formats, or can display Control Framework status and summary\n information. Select a display type to configure a new widget\n
\n
\n selectType(WIDGET_TYPES.BIG_COUNT)}\n compoundIcon={COMPOUND_ICONS.BIG_STAT}\n tooltipContent=\"The count of results returned by a saved search\"\n />\n selectType(WIDGET_TYPES.TABLE)}\n tooltipContent=\"Display the results of a saved search as a table. You can save the table configuration, such as grouping and hidden columns.\"\n />\n selectType(WIDGET_TYPES.RATIO)}\n tooltipContent=\"A comparison of the count of results returned by two saved searches. May be formatted as a percentage, a ratio, or an average.\"\n />\n selectType(WIDGET_TYPES.GAUGE)}\n tooltipContent=\"Display the results of two saved search as a percentage on a gauge. You can configure the marked ranges on the gauge.\"\n />\n selectType(WIDGET_TYPES.LIST)}\n tooltipContent=\"A numbered list of the first results returned from a saved search. You can configure how many items to show and which field will be displayed\"\n />\n selectType(WIDGET_TYPES.REGIONS)}\n tooltipContent=\"A map of cloud region data returned from an advanced search. These searches must be specially formatted and named. \"\n />\n selectType(WIDGET_TYPES.PIE_CHART)}\n tooltipContent=\"A pie chart that displays all the unique result values of the selected field in a saved search\"\n />\n selectType(WIDGET_TYPES.BAR_CHART)}\n tooltipContent=\"A bar chart that displays all the unique result values of the selected field in a saved search\"\n />\n selectType(WIDGET_TYPES.ADVMAP)}\n tooltipContent=\"An map of activity data returned from an advanced search. These searches must be specially formatted and named. \"\n />\n\n {/* \n ---------These are out for now------------\n selectType(WIDGET_TYPES.ALERT)}\n tooltipContent=\"A list of the active alerts for a control framework\"\n />\n selectType(WIDGET_TYPES.SPARK)}\n tooltipContent=\"A chart of the historical compliance level for a control framework\"\n />\n selectType(WIDGET_TYPES.COMPLIANCE)}\n tooltipContent=\"A gauge showing the current compliance percentage of a control framework\"\n /> \n ---------These are out for now------------\n */}\n selectType(WIDGET_TYPES.TEXT)}\n tooltipContent=\"A customizable text widget\"\n />\n
\n
\n )\n}\n\nWidgetTypePicker.propTypes = {\n selectType: PropTypes.func.isRequired,\n}\n\nexport default WidgetTypePicker\n","/*\n * SparkWidget Messages\n * This contains all the text for the SparkWidget component.\n */\nimport { defineMessages } from 'react-intl'\n\nexport default defineMessages({\n header: {\n id: 'app.components.SparkWidget.header',\n defaultMessage: 'This is the SparkWidget component !',\n },\n noHistory: {\n id: 'app.components.SparkWidget.noHistory',\n defaultMessage: 'No History to show',\n },\n total: {\n id: 'app.components.SparkWidget.total',\n defaultMessage: 'Total: ',\n },\n logs: {\n id: 'app.components.SparkWidget.logs',\n defaultMessage: 'Logs',\n },\n})\n","/**\n *\n * SparkWidget\n *\n */\n\nimport React from 'react'\nimport PropTypes from 'prop-types'\nimport Radium from 'radium'\nimport { FormattedMessage } from 'react-intl'\nimport { Query } from 'react-apollo'\nimport { Map } from 'immutable'\nimport messages from './messages'\nimport { exists } from 'utils/sonraiUtils'\nimport Chart from 'components/Chart'\nimport Card, { TopTitle, CardBody } from 'components/Card'\nimport _ from 'lodash'\nimport gql from 'graphql-tag'\nimport WidgetCard from 'components/WidgetCard'\nimport moment from 'moment'\nimport TextLink from 'components/TextLink'\nconst styles = {\n header: { fontSize: '22px', fontWeight: '300' },\n mainContain: {\n height: '100%',\n width: '100%',\n display: 'flex',\n flexDirection: 'column',\n },\n subtitle: {\n alignSelf: 'flex-start',\n fontSize: '1.1rem',\n marginLeft: '0.25rem',\n color: 'rgb(125, 120, 120)',\n },\n chartContain: { height: '90%', width: '100%', padding: '0.5rem' },\n}\n\nclass SparkWidget extends React.Component {\n getData = data => {\n if (exists(data.ControlFrameworkEvalLogs)) {\n const logs = data.ControlFrameworkEvalLogs.items.filter(\n x =>\n x.controlFrameworkId ==\n this.props.resultLayout.widgetOptions.controlFramework.value\n )\n if (!_.isEmpty(logs)) {\n const x = logs.map(log => moment.unix(log.time).format())\n const y = logs.map(log => log.percentPass)\n return { x, y }\n } else {\n return {}\n }\n }\n }\n\n getId = data => {\n if (exists(data.ControlFrameworkEvalLogs)) {\n if (!_.isEmpty(data.ControlFrameworkEvalLogs.items)) {\n return this.props.resultLayout.widgetOptions.controlFramework.value\n }\n }\n }\n\n handleClick = id => {\n this.props.onClickControlFramework(id)\n }\n\n renderChart = (logs, id) => {\n if (_.isEmpty(logs)) {\n return (\n \n \n
\n )\n } else {\n return (\n \n
\n this.handleClick(id)}>\n \n {logs.x.length} \n \n
\n
\n _.round(num, 1)) : logs.y\n }\n xLabels={logs.x}\n />\n
\n
\n )\n }\n }\n\n getQuery = () => {\n const { widgetOptions } = this.props.resultLayout\n if (exists(widgetOptions)) {\n if (widgetOptions.controlFramework) {\n const query = {\n doc: gql`\n query controlFrameLogsOverTime {\n ControlFrameworkEvalLogs(where: { \n controlFrameworkId: \"${widgetOptions.controlFramework.value}\", \n from: \"${moment()\n .subtract(widgetOptions.sparkTimeFrame.value, 'days')\n .format('YYYY-MM-DD')}\"\n }) {\n items {\n id\n controlFrameworkId\n percentPass\n time\n }\n }\n }\n `,\n }\n return query\n }\n }\n }\n\n renderHeader = id => {\n if (exists(id)) {\n return (\n this.handleClick(id)}>\n {this.props.title}
\n \n )\n } else {\n return (\n \n )\n }\n }\n\n getSearchId = () => {\n //Because it doesn't support saved or sonrai searches - just control frameworks\n return Map({\n uiSearch: null,\n advancedSearch: null,\n })\n }\n\n render() {\n if (this.props.data === undefined) {\n const searchId = this.getSearchId()\n const query = this.getQuery()\n if (!query.doc) {\n return (\n \n )\n }\n\n return (\n \n {({ loading, error, data, networkStatus, refetch }) => {\n const logs = loading ? null : this.getData(data)\n const id = loading ? null : this.getId(data)\n\n return (\n this.handleClick(id)}>\n {this.props.title}\n \n }\n description={_.get(this.props.resultLayout, [\n 'widgetOptions',\n 'description',\n ])}\n >\n {this.renderHeader(id)}\n {this.renderChart(logs, id)}\n \n )\n }}\n \n )\n } else {\n return (\n \n {this.props.title && {this.renderHeader()}}\n {this.renderChart(this.props.data)}\n \n )\n }\n }\n}\n\nSparkWidget = Radium(SparkWidget) // eslint-disable-line\n\nSparkWidget.propTypes = {\n allowDelete: PropTypes.bool,\n allowUpdate: PropTypes.bool,\n disableToolbar: PropTypes.bool,\n title: PropTypes.string.isRequired,\n onRemove: PropTypes.func,\n toggleStatic: PropTypes.func,\n static: PropTypes.bool,\n resultLayout: PropTypes.object,\n data: PropTypes.object,\n onEdit: PropTypes.func,\n onClickControlFramework: PropTypes.func,\n widget: PropTypes.object,\n}\n\nexport default SparkWidget\n","import React from 'react'\nimport PropTypes from 'prop-types'\nimport { Input } from 'reactstrap'\nimport { Map } from 'immutable'\nimport _ from 'lodash'\n\nimport SparkWidget from 'components/SparkWidget'\nimport FormLabel from 'components/FormLabel'\nimport SelectBar from 'components/SelectBar'\nimport PreviewContainer from './PreviewContainer'\n\nconst styles = {\n wrapperWithPreview: {\n display: 'grid',\n gridTemplateColumns: '50% 1fr',\n gridColumnGap: '2em',\n },\n sparkPreview: { minWidth: '450px', height: '260px', maxHeight: '260px' },\n}\n\nconst placeholderData = {\n x: ['01-01', '01-02', '01-03', '01-04', '01-05', '01-06', '01-07'],\n y: [43, 23, 54, 77, 99, 21, 4],\n}\n\nclass SparkWidgetConfig extends React.Component {\n componentDidUpdate() {\n this.updateValidity()\n }\n\n updateValidity = () => {\n const hasControlFrameWork = !!this.props.widgetOptions.controlFramework\n const hasTitle = this.props.widgetTitle\n const hasTimeFrame = !!this.props.widgetOptions.sparkTimeFrame\n\n const valid = hasControlFrameWork && hasTitle && hasTimeFrame\n this.props.setValidity(valid)\n }\n\n handleSelectControlFrameWork = val => {\n this.props.setWidgetOptions({\n controlFramework: val,\n })\n\n this.props.widgetOptions.sparkTimeFrame &&\n this.handleSuggestedTitle(\n this.getSuggestedTitle(val, this.props.widgetOptions.sparkTimeFrame)\n )\n }\n\n handleSelectSparkTimeFrame = val => {\n this.props.setWidgetOptions({\n sparkTimeFrame: val,\n })\n\n this.props.widgetOptions.controlFramework &&\n this.handleSuggestedTitle(\n this.getSuggestedTitle(this.props.widgetOptions.controlFramework, val)\n )\n }\n\n setTitle = e => {\n this.props.setWidgetTitle(e.target.value)\n }\n\n setDescription = e => {\n this.props.setWidgetOptions({\n description: e.target.value,\n })\n }\n\n handleSuggestedTitle = title => this.props.setWidgetTitle(title)\n\n getSuggestedTitle = (framework, time) => {\n if (framework !== null && time !== null) {\n const message = `${time.label} of ${framework.label}`\n return message\n }\n }\n\n render() {\n const controlFrameWorkOptions = this.props.controlGroupOptions\n .filter(cf => cf.get('enabled'))\n .toList()\n .map(cf =>\n Map({\n value: cf.get('srn'),\n label: cf.get('title'),\n })\n )\n .toJS()\n\n const timeFrameOptions = [\n { value: 1, label: 'Last 24 Hours' },\n { value: 7, label: 'Last 7 Days' },\n {\n value: 30,\n label: 'Last 30 Days',\n },\n {\n value: 90,\n label: 'Last 90 Days',\n },\n ]\n\n return (\n \n )\n }\n}\n\nSparkWidgetConfig.propTypes = {\n previewWidget: PropTypes.bool,\n setWidgetOptions: PropTypes.func,\n setWidgetTitle: PropTypes.func,\n setValidity: PropTypes.func,\n widgetOptions: PropTypes.shape({\n controlFramework: PropTypes.object,\n description: PropTypes.string,\n sparkTimeFrame: PropTypes.object,\n }),\n\n widgetTitle: PropTypes.string,\n controlGroupOptions: PropTypes.object,\n}\n\nexport default SparkWidgetConfig\n","/*\n * ComplianceWidget Messages\n *\n * This contains all the text for the ComplianceWidget component.\n */\nimport { defineMessages } from 'react-intl'\n\nexport default defineMessages({\n header: {\n id: 'app.components.ComplianceWidget.header',\n defaultMessage: 'This is the ComplianceWidget component !',\n },\n noData: {\n id: 'app.components.ComplianceWidget.noData',\n defaultMessage: 'No Data to show',\n },\n})\n","/**\n *\n * ComplianceWidget\n *\n */\n\nimport React from 'react'\nimport PropTypes from 'prop-types'\nimport { Query } from 'react-apollo'\nimport messages from './messages'\nimport { exists } from 'utils/sonraiUtils'\nimport Chart from 'components/Chart'\nimport Card, { TopTitle, CardBody } from 'components/Card'\nimport _ from 'lodash'\nimport gql from 'graphql-tag'\nimport WidgetCard from 'components/WidgetCard'\nimport { round } from 'utils/widgetUtils'\nimport { FormattedMessage } from 'react-intl'\nimport TextLink from 'components/TextLink'\nimport { Map } from 'immutable'\nconst styles = {\n header: { fontSize: '1.1rem', fontWeight: '300' },\n chartContainer: {\n width: '100%',\n height: '100%',\n display: 'flex',\n justifyContent: 'center',\n alignItems: 'center',\n },\n}\n\nclass ComplianceWidgets extends React.Component {\n getData = data => {\n if (exists(data.ControlFrameworkEvalLogs)) {\n if (!_.isEmpty(data.ControlFrameworkEvalLogs.items)) {\n const percentage = data.ControlFrameworkEvalLogs.items[0].percentPass\n if (exists(percentage)) {\n return [round(percentage, 1)]\n } else {\n return []\n }\n }\n }\n }\n\n renderChart = value => {\n if (_.isEmpty(value)) {\n return (\n \n \n
\n )\n } else {\n return (\n \n )\n }\n }\n\n getQuery = () => {\n const { widgetOptions } = this.props.resultLayout\n if (exists(widgetOptions)) {\n if (widgetOptions.controlFramework) {\n return gql`\n query getCompliancePercentage {\n ControlFrameworkEvalLogs(\n where: {\n controlFrameworkId: \"${\n this.props.resultLayout.widgetOptions.controlFramework.value\n }\"\n limit: 1\n }\n ) {\n items {\n \n percentPass\n }\n }\n }\n `\n }\n }\n }\n\n renderHeader = id => {\n if (exists(id)) {\n return (\n this.props.onClickControlFramework(id)}>\n {this.props.title}
\n \n )\n } else {\n return (\n \n )\n }\n }\n\n getSearchId = () => {\n //Because it doesn't support saved or sonrai searches - just control frameworks\n return Map({\n uiSearch: null,\n advancedSearch: null,\n })\n }\n\n render() {\n if (this.props.data === undefined) {\n const searchId = this.getSearchId()\n const query = this.getQuery()\n if (!query) {\n return (\n \n )\n }\n\n return (\n \n {({ loading, error, data, networkStatus }) => {\n const value = loading ? null : this.getData(data)\n const id = exists(this.props.resultLayout.widgetOptions)\n ? this.props.resultLayout.widgetOptions.controlFramework.value\n : undefined\n\n return (\n this.props.onClickControlFramework(id)}\n >\n {this.props.title}\n \n }\n description={_.get(this.props.resultLayout, [\n 'widgetOptions',\n 'description',\n ])}\n >\n {this.renderHeader(id)}\n \n \n {this.renderChart(value)}\n
\n \n \n )\n }}\n \n )\n } else {\n return (\n \n {this.props.title && {this.renderHeader()}}\n \n \n {this.renderChart(this.props.data)}\n
\n \n \n )\n }\n }\n}\n\nComplianceWidgets.propTypes = {\n allowDelete: PropTypes.bool,\n allowUpdate: PropTypes.bool,\n data: PropTypes.object,\n disableToolbar: PropTypes.bool,\n noAnimate: PropTypes.bool,\n title: PropTypes.string,\n onChartReady: PropTypes.func,\n onClickControlFramework: PropTypes.func,\n onEdit: PropTypes.func,\n onRemove: PropTypes.func,\n resultLayout: PropTypes.shape({\n widgetOptions: PropTypes.shape({\n controlFramework: PropTypes.shape({\n value: PropTypes.string,\n }),\n }),\n }),\n static: PropTypes.bool,\n toggleStatic: PropTypes.bool,\n widget: PropTypes.object,\n}\n\nexport default ComplianceWidgets\n","import React from 'react'\nimport PropTypes from 'prop-types'\nimport { Input } from 'reactstrap'\nimport { Map } from 'immutable'\nimport _ from 'lodash'\n\nimport ComplianceWidget from 'components/ComplianceWidget'\nimport FormLabel from 'components/FormLabel'\nimport SelectBar from 'components/SelectBar'\nimport PreviewContainer from './PreviewContainer'\n\nconst styles = {\n wrapperWithPreview: {\n display: 'grid',\n gridTemplateColumns: '45% 1fr',\n gridColumnGap: '2em',\n },\n compliancePreview: { minWidth: '225px', height: '350px', maxHeight: '350px' },\n}\n\nclass ComplianceWidgetConfig extends React.Component {\n componentDidUpdate(oldProps) {\n if (\n _.get(this.props.widgetOptions.controlFramework, 'value') !==\n _.get(oldProps.widgetOptions.controlFramework, 'value') ||\n this.props.widgetTitle !== oldProps.widgetTitle\n ) {\n this.updateValidity()\n }\n }\n\n updateValidity = () => {\n const hasControlFrameWork = !!this.props.widgetOptions.controlFramework\n const hasTitle = this.props.widgetTitle\n\n const valid = hasControlFrameWork && hasTitle\n this.props.setValidity(valid)\n }\n\n handleSelectControlFrameWork = val => {\n this.props.setWidgetOptions({\n controlFramework: val,\n })\n\n this.handleSuggestedTitle(this.getSuggestedTitle(val))\n }\n\n setTitle = e => {\n this.props.setWidgetTitle(e.target.value)\n }\n\n setDescription = e => {\n this.props.setWidgetOptions({\n description: e.target.value,\n })\n }\n\n handleSuggestedTitle = title => this.props.setWidgetTitle(title)\n\n getSuggestedTitle = selection => {\n if (selection !== null) {\n const message = `${selection.label} Compliance`\n return message\n }\n }\n\n render() {\n const controlFrameWorkOptions = this.props.controlGroupOptions\n .filter(cf => cf.get('enabled'))\n .toList()\n .map(cf =>\n Map({\n value: cf.get('srn'),\n label: cf.get('title'),\n })\n )\n .toJS()\n\n return (\n \n )\n }\n}\n\nComplianceWidgetConfig.propTypes = {\n previewWidget: PropTypes.bool,\n setWidgetOptions: PropTypes.func,\n setWidgetTitle: PropTypes.func,\n setValidity: PropTypes.func,\n widgetOptions: PropTypes.shape({\n controlFramework: PropTypes.object,\n description: PropTypes.string,\n }),\n\n widgetTitle: PropTypes.string,\n controlGroupOptions: PropTypes.object,\n}\n\nexport default ComplianceWidgetConfig\n","import React from 'react'\nimport PropTypes from 'prop-types'\nimport { Input } from 'reactstrap'\nimport FormLabel from 'components/FormLabel'\nimport MarkdownEditor from 'components/MarkdownEditor'\n\nclass TextWidgetConfig extends React.Component {\n setTitle = e => {\n this.props.setWidgetTitle(e.target.value)\n }\n\n componentDidUpdate() {\n this.updateValidity()\n }\n\n updateValidity = () => {\n const hasTitle = !!this.props.widgetTitle\n const hasMarkdown = !!this.props.widgetOptions.description\n const isValid = hasTitle && hasMarkdown\n this.props.setValidity(isValid)\n }\n\n render() {\n return (\n \n
\n \n \n\n \n\n \n this.props.setWidgetOptions({\n description: value,\n })\n }\n />\n
\n
\n )\n }\n}\n\nTextWidgetConfig.propTypes = {\n setWidgetOptions: PropTypes.func,\n setWidgetTitle: PropTypes.func,\n widgetOptions: PropTypes.shape({\n description: PropTypes.string,\n }),\n setValidity: PropTypes.func,\n widgetTitle: PropTypes.string,\n}\n\nexport default TextWidgetConfig\n","/**\n *\n * WidgetModal\n *\n */\n\nimport React from 'react'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport PropTypes from 'prop-types'\nimport { connect } from 'react-redux'\nimport { FormattedMessage } from 'react-intl'\nimport { createStructuredSelector } from 'reselect'\nimport { compose, bindActionCreators } from 'redux'\nimport { Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap'\nimport { Map } from 'immutable'\n\nimport Breadcrumb, { BreadcrumbItem } from 'components/Breadcrumb'\nimport TextLink from 'components/TextLink'\nimport Button from 'components/Button'\nimport { WIDGET_TYPES } from 'appConstants'\nimport {\n selectSavedSearches,\n selectSonraiSearches,\n selectQueryTypes,\n} from 'containers/SonraiData/selectors'\nimport { selectControlGroups } from 'containers/ControlFrameworkData/selectors'\n\nimport injectReducer from 'utils/injectReducer'\nimport injectSaga from 'utils/injectSaga'\nimport {\n updateEditWidget,\n addSCWidget,\n} from 'containers/SolutionCenter/actions'\nimport Icon from 'components/Icon'\n\nimport {\n selectSearchCardsBySearchId,\n selectWidgetSize,\n selectWidgetType,\n selectWidgetTitle,\n selectWidgetOptions,\n selectWidgetSelector,\n selectWidgetSubTitle,\n selectPreviewWidget,\n selectWidgetResultLayout,\n} from './selectors'\nimport reducer from './reducer'\nimport messages from './messages'\nimport {\n setWidgetTitle,\n setWidgetSubTitle,\n setWidgetType,\n setWidgetSelector,\n setWidgetFormatter,\n setWidgetSearchField,\n setWidgetSavedSearch,\n clearWidgetModal,\n togglePreviewWidget,\n setWidgetSize,\n setWidgetOptions,\n setWidgetSonraiSearch,\n loadWidget,\n} from './actions'\nimport sagas from './sagas'\nimport RatioConfig from './RatioConfig'\nimport BigCountConfig from './BigCountConfig'\nimport GaugeConfig from './GaugeConfig'\nimport RegionsWidgetConfig from './RegionsWidgetConfig'\nimport TableConfig from './TableConfig'\nimport ListWidgetConfig from './ListWidgetConfig'\nimport PieChartWidgetConfig from './PieChartWidgetConfig'\nimport BarChartWidgetConfig from './BarChartWidgetConfig'\nimport MapWidgetConfig from './AdvMapWidgetConfig'\nimport AlertWidgetConfig from './AlertWidgetConfig'\nimport WidgetModalErrorBoundary from './WidgetModalErrorBoundary'\nimport WidgetTypePicker from './WidgetTypePicker'\nimport SparkWidgetConfig from './SparkWidgetConfig'\nimport ComplianceWidgetConfig from './ComplianceWidgetConfig'\nimport TextWidgetConfig from './TextWidgetConfig'\n\nconst hasPreview = [\n 'bigCount',\n 'barChart',\n 'pieChart',\n 'alert',\n 'spark',\n 'compliance',\n 'gauge',\n 'ratio',\n 'list',\n]\n\nconst styles = {\n footerTextContainer: {\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'center',\n width: '100%',\n },\n previewModalContainer: {\n display: 'flex',\n justifyContent: 'center',\n minWidth: '1100px',\n },\n footerContainer: {\n display: 'flex',\n flexDirection: 'column',\n justifyContent: 'space-between',\n position: 'relative',\n },\n breadcrumbContainer: {\n marginBottom: '1em',\n },\n}\n\nexport class WidgetModal extends React.PureComponent {\n state = {\n valid: false,\n }\n\n componentDidUpdate(oldProps) {\n if (\n !oldProps.isOpen &&\n this.props.isOpen &&\n !this.props.editingWidget.isEmpty()\n ) {\n this.props.loadWidget(this.props.editingWidget)\n }\n\n if (oldProps.isOpen && !this.props.isOpen) {\n this.props.clearWidgetModal()\n }\n }\n\n componentWillUnmount() {\n this.props.clearWidgetModal()\n }\n\n setValidity = valid => {\n this.setState({\n valid,\n })\n }\n\n getSelector = (widgetSelector, widgetType) => {\n if (widgetSelector) {\n return `${widgetSelector}`\n } else {\n return WIDGET_TYPES[widgetType]\n }\n }\n\n updateWidget = () => {\n const {\n widgetType,\n widgetTitle,\n widgetSubTitle,\n widgetSize,\n widgetSelector,\n widgetResultLayout,\n widgetOptions,\n } = this.props\n\n this.props.updateEditWidget(this.props.card, {\n sid: this.props.editingWidget.get('sid'),\n srn: this.props.editingWidget.get('srn'),\n title: widgetTitle,\n subtitle: widgetSubTitle,\n type: widgetType,\n widgetSize: widgetSize,\n resultLayout: widgetResultLayout,\n selection: this.getSelector(widgetSelector, widgetType),\n contains: {\n add: Object.values(widgetResultLayout.indexedSearches),\n },\n options: widgetOptions,\n })\n }\n\n addWidget = () => {\n const {\n widgetType,\n widgetTitle,\n widgetSubTitle,\n widgetSize,\n widgetSelector,\n widgetResultLayout,\n widgetOptions,\n } = this.props\n\n const searches = Object.values(\n widgetResultLayout.indexedSearches\n ).map(searchSID => this.props.savedSearches.get(searchSID).get('srn'))\n\n this.props.addSCWidget(this.props.card, {\n title: widgetTitle,\n subtitle: widgetSubTitle,\n type: widgetType,\n resultLayout: widgetResultLayout,\n selection: this.getSelector(widgetSelector, widgetType),\n widgetSize: [widgetSize[0], widgetSize[1]],\n widgetLocation: [0, 0],\n static: false,\n contains: { add: searches },\n options: widgetOptions,\n })\n }\n\n setWidgetType = e => {\n const type = e.target.value\n this.selectType(type)\n }\n\n resetType = () => {\n this.props.setWidgetType(null)\n if (this.props.previewWidget) {\n this.props.togglePreviewWidget()\n }\n }\n\n selectType = type => {\n this.props.setWidgetType(type)\n if (\n (hasPreview.includes(type) && !this.props.previewWidget) ||\n (!hasPreview.includes(type) && this.props.previewWidget)\n ) {\n this.props.togglePreviewWidget()\n }\n\n // [Width, Height]\n if (type === WIDGET_TYPES.TABLE || type === WIDGET_TYPES.TEXT) {\n this.props.setWidgetSize([20, 12])\n } else if (type === WIDGET_TYPES.GAUGE) {\n this.props.setWidgetSize([12, 8])\n } else if (type === WIDGET_TYPES.REGIONS) {\n this.props.setWidgetSize([20, 12])\n } else if (type === WIDGET_TYPES.ALERT) {\n this.props.setWidgetSize([14, 11])\n } else if (type === WIDGET_TYPES.ADVMAP) {\n this.props.setWidgetSize([30, 18])\n } else if (type === WIDGET_TYPES.LIST) {\n this.props.setWidgetSize([8, 12])\n } else if (type === WIDGET_TYPES.SPARK) {\n this.props.setWidgetSize([28, 8])\n } else if (type === WIDGET_TYPES.COMPLIANCE) {\n this.props.setWidgetSize([10, 8])\n } else {\n this.props.setWidgetSize([8, 6])\n }\n }\n\n setWidgetSearchField = (field, index) => {\n this.props.setWidgetSearchField(field, index, this.props.savedSearches)\n }\n\n getConfigComponent = () => {\n const type = this.props.widgetType\n switch (type) {\n case WIDGET_TYPES.BIG_COUNT:\n return BigCountConfig\n case WIDGET_TYPES.TABLE:\n return TableConfig\n case WIDGET_TYPES.RATIO:\n return RatioConfig\n case WIDGET_TYPES.GAUGE:\n return GaugeConfig\n case WIDGET_TYPES.LIST:\n return ListWidgetConfig\n case WIDGET_TYPES.REGIONS:\n return RegionsWidgetConfig\n case WIDGET_TYPES.PIE_CHART:\n return PieChartWidgetConfig\n case WIDGET_TYPES.BAR_CHART:\n return BarChartWidgetConfig\n case WIDGET_TYPES.ADVMAP:\n return MapWidgetConfig\n case WIDGET_TYPES.ALERT:\n return AlertWidgetConfig\n case WIDGET_TYPES.SPARK:\n return SparkWidgetConfig\n case WIDGET_TYPES.COMPLIANCE:\n return ComplianceWidgetConfig\n case WIDGET_TYPES.TEXT:\n return TextWidgetConfig\n default:\n return null\n }\n }\n\n getWidgetTypeLabel = () => {\n switch (this.props.widgetType) {\n case WIDGET_TYPES.BIG_COUNT:\n return 'Big Count'\n case WIDGET_TYPES.TABLE:\n return 'Table'\n case WIDGET_TYPES.RATIO:\n return 'Ratio'\n case WIDGET_TYPES.GAUGE:\n return 'Gauge'\n case WIDGET_TYPES.LIST:\n return 'List'\n case WIDGET_TYPES.REGIONS:\n return 'Regions'\n case WIDGET_TYPES.PIE_CHART:\n return 'Pie Chart'\n case WIDGET_TYPES.BAR_CHART:\n return 'Bar Chart'\n case WIDGET_TYPES.ADVMAP:\n return 'Arc Map'\n case WIDGET_TYPES.ALERT:\n return 'Alerts'\n case WIDGET_TYPES.SPARK:\n return 'Compliance over Time'\n case WIDGET_TYPES.COMPLIANCE:\n return 'Current Compliance'\n case WIDGET_TYPES.TEXT:\n return 'Text'\n default:\n return ''\n }\n }\n\n getWidgetInputs = () => {\n const ConfigComponent = this.getConfigComponent()\n\n if (!ConfigComponent) {\n return null\n }\n\n return (\n \n \n \n )\n }\n\n renderContent = () => {\n if (!this.props.widgetType) {\n return \n }\n\n return (\n \n {this.props.editingWidget.isEmpty() && (\n
\n \n \n \n Widget Types\n \n \n {this.getWidgetTypeLabel()}\n \n
\n )}\n
{this.getWidgetInputs()}
\n
\n )\n }\n\n render() {\n const widgetCanPreview = hasPreview.includes(this.props.widgetType)\n const isDisabled =\n !this.state.valid || this.props.saving || !this.props.widgetType\n\n return (\n \n \n {this.props.editingWidget.isEmpty() ? (\n \n ) : (\n \n )}\n \n\n {this.renderContent()}\n \n \n {widgetCanPreview && (\n
\n {this.props.previewWidget\n ? 'Hide Widget Preview'\n : 'Show Widget Preview'}\n \n )}\n
\n \n Cancel\n \n {' '}\n
\n
\n \n \n )\n }\n}\n\nWidgetModal.defaultProps = {\n editingWidget: Map(),\n}\n\nWidgetModal.propTypes = {\n controlGroupOptions: PropTypes.object,\n editingWidget: ImmutablePropTypes.map.isRequired,\n getQueryBuilder: PropTypes.func.isRequired,\n isOpen: PropTypes.bool,\n onClose: PropTypes.func.isRequired,\n loadWidget: PropTypes.func.isRequired,\n previewWidget: PropTypes.bool,\n queryTypes: ImmutablePropTypes.map,\n savedSearches: ImmutablePropTypes.map.isRequired,\n saving: PropTypes.bool,\n searchCards: ImmutablePropTypes.map.isRequired,\n setWidgetOptions: PropTypes.func.isRequired,\n setWidgetTitle: PropTypes.func.isRequired,\n setWidgetSize: PropTypes.func.isRequired,\n setWidgetSubTitle: PropTypes.func.isRequired,\n setWidgetType: PropTypes.func.isRequired,\n setWidgetSelector: PropTypes.func.isRequired,\n setWidgetFormatter: PropTypes.func.isRequired,\n setWidgetSearchField: PropTypes.func.isRequired,\n setWidgetSavedSearch: PropTypes.func.isRequired,\n card: PropTypes.shape({\n sid: PropTypes.string.isRequired,\n srn: PropTypes.string.isRequired,\n }).isRequired,\n layout: PropTypes.array.isRequired,\n clearWidgetModal: PropTypes.func.isRequired,\n addSCWidget: PropTypes.func.isRequired,\n setWidgetSonraiSearch: PropTypes.func.isRequired,\n sonraiSearches: ImmutablePropTypes.list.isRequired,\n saveCheckBox: PropTypes.func,\n togglePreviewWidget: PropTypes.func.isRequired,\n updateEditWidget: PropTypes.func,\n widgetType: PropTypes.string,\n widgetTitle: PropTypes.string,\n widgetSubTitle: PropTypes.string,\n widgetSize: PropTypes.array,\n widgetSelector: PropTypes.string,\n widgetResultLayout: PropTypes.object,\n widgetOptions: PropTypes.object,\n}\n\nconst mapStateToProps = createStructuredSelector({\n controlGroupOptions: selectControlGroups,\n previewWidget: selectPreviewWidget,\n queryTypes: selectQueryTypes,\n savedSearches: selectSavedSearches,\n sonraiSearches: selectSonraiSearches,\n searchCards: selectSearchCardsBySearchId,\n widgetType: selectWidgetType,\n widgetTitle: selectWidgetTitle,\n widgetSubTitle: selectWidgetSubTitle,\n widgetSize: selectWidgetSize,\n widgetSelector: selectWidgetSelector,\n widgetResultLayout: selectWidgetResultLayout,\n widgetOptions: selectWidgetOptions,\n})\n\nfunction mapDispatchToProps(dispatch) {\n return bindActionCreators(\n {\n addSCWidget,\n setWidgetOptions,\n setWidgetTitle,\n setWidgetSize,\n setWidgetSubTitle,\n setWidgetType,\n togglePreviewWidget,\n setWidgetSelector,\n setWidgetFormatter,\n setWidgetSearchField,\n setWidgetSavedSearch,\n clearWidgetModal,\n setWidgetSonraiSearch,\n loadWidget,\n updateEditWidget,\n },\n dispatch\n )\n}\n\nconst withConnect = connect(mapStateToProps, mapDispatchToProps)\nconst withReducer = injectReducer({ key: 'WidgetModal', reducer })\nconst withSaga = injectSaga({ key: 'WidgetModal', saga: sagas })\n\nexport default compose(withReducer, withConnect, withSaga)(WidgetModal)\n","import { fromJS } from 'immutable'\nimport { handleActions } from 'redux-actions'\n\nimport {\n ADD_SC_WIDGET,\n UPDATE_EDIT_WIDGET,\n UPDATE_WIDGET_SUCCESS,\n ADD_WIDGET_SUCCESS,\n} from 'containers/SolutionCenter/constants'\n\nconst initialState = fromJS({\n savingWidget: false,\n})\n\nconst SolutionCenterWidgetsReducer = handleActions(\n {\n [ADD_SC_WIDGET]: state => state.set('savingWidget', true),\n [ADD_WIDGET_SUCCESS]: state => state.set('savingWidget', false),\n [UPDATE_EDIT_WIDGET]: state => state.set('savingWidget', true),\n [UPDATE_WIDGET_SUCCESS]: state => state.set('savingWidget', false),\n },\n initialState\n)\n\nexport default SolutionCenterWidgetsReducer\n","import { createSelector } from 'reselect'\nimport { List } from 'immutable'\nimport { WIDGET_TYPES } from 'appConstants'\n\nexport const selectSolutionCenterWidgetsDomain = state =>\n state.get('SolutionCenterWidgets')\n\nexport const selectSavingWidget = createSelector(\n selectSolutionCenterWidgetsDomain,\n state => state.get('savingWidget', false)\n)\n\nconst getMinWidgetHeight = widgetType => {\n switch (widgetType) {\n case WIDGET_TYPES.LIST:\n return 6\n case WIDGET_TYPES.TABLE:\n return 5\n case WIDGET_TYPES.REGIONS:\n return 8\n case WIDGET_TYPES.PIE_CHART:\n return 6\n case WIDGET_TYPES.BAR_CHART:\n return 6\n default:\n return 4\n }\n}\n\nconst getMinWidgetWidth = widgetType => {\n switch (widgetType) {\n case WIDGET_TYPES.LIST:\n return 6\n case WIDGET_TYPES.TABLE:\n return 12\n case WIDGET_TYPES.GAUGE:\n return 8\n case WIDGET_TYPES.REGIONS:\n return 15\n case WIDGET_TYPES.PIE_CHART:\n return 6\n case WIDGET_TYPES.BAR_CHART:\n return 6\n default:\n return 4\n }\n}\n\nexport const selectCard = (state, props) => props.card\nexport const selectWidgets = createSelector(\n selectCard,\n card => card.get('widgets', List()).toJS() || []\n)\n\nexport const selectLayout = createSelector(\n selectWidgets,\n widgets => {\n return widgets\n .map(widget => {\n if (widget.widgetSize) {\n return {\n w: widget.widgetSize[0],\n h: widget.widgetSize[1],\n x: widget.widgetLocation[0],\n y: widget.widgetLocation[1],\n i: widget.sid,\n static: widget.static,\n moved: false,\n isResizable: undefined,\n isDraggable: undefined,\n maxH: undefined,\n maxW: undefined,\n minH: getMinWidgetHeight(widget.type),\n minW: getMinWidgetWidth(widget.type),\n }\n }\n })\n .filter(widget => !!widget)\n }\n)\n","import { defineMessages } from 'react-intl'\n\nexport default defineMessages({\n err: {\n id: 'app.components.RegionsWidget.err',\n defaultMessage: 'This widget has been misconfigured.',\n },\n})\n","/**\n *\n * RegionsMap\n *\n */\n\nimport React from 'react'\nimport PropTypes from 'prop-types'\nimport _ from 'lodash'\nimport { scaleLinear } from 'd3-scale'\nimport Color from 'color'\nimport Button from 'components/Button'\nimport geoObj from 'assets/world-50m.json'\nimport Icon from 'components/Icon'\n\nimport {\n ComposableMap,\n ZoomableGroup,\n Geographies,\n Geography,\n Markers,\n Marker,\n} from 'react-simple-maps'\nimport { getCoordConf } from 'utils/widgetUtils'\nimport themeable, { themeShape } from 'containers/ThemeManager/Themeable'\n\nclass RegionsMap extends React.Component {\n constructor(props) {\n super(props)\n this.state = {\n zoom: 1,\n activeMarker: null,\n selectedMarker: null,\n itemsHover: false,\n }\n this.styles = {\n mapContainer: {\n overflow: 'hidden',\n },\n mapGrid: {\n display: 'grid',\n gridTemplateColumns: 'auto auto',\n overflow: 'hidden',\n },\n tooltip: {\n fill: '#fff',\n filter: 'url(#shadow)',\n },\n zoomButton: {\n color: 'white',\n padding: '0.5rem 1rem',\n },\n zoomBtnCont: {\n bottom: '0px',\n right: '0px',\n position: 'absolute',\n },\n legendHighlight: {\n color: this.props.theme.primary,\n borderRadius: '1em',\n cursor: 'pointer',\n },\n legendHeader: {\n paddingLeft: '5px',\n backgroundColor: this.props.theme.neutralLight,\n },\n legend: {\n overflowY: 'auto',\n padding: '0.5em',\n height: '100%',\n },\n svg: {\n fontFamily: '\"Font Awesome 5 Pro\"',\n fontWeight: 300,\n userSelect: 'none',\n cursor: 'pointer',\n },\n svgItems: {\n fontSize: '11px',\n cursor: 'pointer',\n },\n svgItemsHighlight: {\n fontSize: '11px',\n cursor: 'pointer',\n color: this.props.theme.primary,\n },\n }\n }\n\n handleZoomIn = () => {\n this.setState(currentState => ({\n zoom: currentState.zoom * 1.25,\n }))\n }\n\n handleZoomOut = () => {\n this.setState(currentState => ({\n zoom: currentState.zoom / 1.25,\n }))\n }\n\n handleMarkerHover = value => {\n this.setState({\n activeMarker: value,\n })\n }\n\n handleMarkerExit = value => {\n if (value === this.state.activeMarker) {\n this.setState({\n activeMarker: null,\n })\n }\n }\n\n handleOnClickRegion = value => {\n if (this.state.selectedMarker === value) {\n this.setState({ selectedMarker: null })\n } else {\n this.setState({ selectedMarker: value })\n }\n }\n\n handleDeselect = () => {\n this.setState({ selectedMarker: null })\n }\n\n getMarker = (value, count, scale) => {\n const coordConf = getCoordConf(value, this.props.theme)\n const coordinates = coordConf.coordinates\n\n if (coordinates) {\n const hovered = this.state.activeMarker === value\n return (\n \n \n \n )\n } else {\n return null\n }\n }\n\n getRegionSvgLabel = title => {\n const limit = 21 // the legal age of booze in america\n return title.length > limit ? `${title.substr(0, limit - 1)}...` : title\n }\n\n enterItems = () => {\n this.setState({ itemsHover: true })\n }\n\n leaveItems = () => {\n this.setState({ itemsHover: false })\n }\n\n getToolTip = (value, count) => {\n const coordConf = getCoordConf(value, this.props.theme)\n const coordinates = coordConf.coordinates\n\n if (!coordinates) {\n return null\n }\n\n return (\n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n {count} items\n \n \n {this.getRegionSvgLabel(coordConf.label)}\n \n \n {coordConf.type}\n \n \n \n \n )\n }\n\n renderGeography = (geographies, projection) => {\n const baseStyle = {\n fill: this.props.theme.neutralLight,\n stroke: this.props.theme.neutralMedium,\n strokeWidth: 0.5,\n outline: 'none',\n }\n\n return geographies\n .filter(geo => geo.properties.NAME !== 'Antarctica')\n .map((geography, i) => (\n \n ))\n }\n\n getMarkers = () => {\n const { regionCounts } = this.props\n const counts = _.toArray(regionCounts).sort((a, b) => a - b)\n const maxCount = counts.pop()\n\n const scale = scaleLinear()\n .domain([0, maxCount])\n .range([4, 20])\n\n scale.clamp(true)\n\n const selectedMarkerCount = this.state.selectedMarker\n ? regionCounts[this.state.selectedMarker]\n : null\n\n const markers = Object.keys(regionCounts)\n .map(key => this.getMarker(key, regionCounts[key], scale))\n .filter(node => !!node)\n\n if (this.state.selectedMarker) {\n markers.push(\n this.getToolTip(this.state.selectedMarker, selectedMarkerCount)\n )\n }\n\n return {markers}\n }\n\n getLegend = () => {\n let { regionCounts } = this.props\n\n const all = Object.keys(regionCounts)\n .filter(key => key !== 'null')\n .sort()\n let byType = {}\n\n all.forEach(region => {\n const conf = getCoordConf(region)\n if (!byType[conf.type]) {\n byType[conf.type] = []\n }\n byType[conf.type].push(region)\n })\n\n let legend = {}\n\n Object.keys(byType).forEach(cloudType => {\n if (!legend[cloudType]) {\n legend[cloudType] = []\n }\n\n byType[cloudType].forEach(region => {\n legend[cloudType].push(\n this.handleMarkerHover(region)}\n onPointerOut={() => this.handleMarkerExit(region)}\n onClick={() => this.handleOnClickRegion(region)}\n >\n {_.startCase(region)}\n
\n )\n })\n })\n\n return (\n \n {Object.keys(legend).map(type => {\n return (\n
\n
{type}
\n
\n {legend[type].map(thisLeg => {\n return thisLeg\n })}\n
\n
\n )\n })}\n
\n )\n }\n\n shouldComponentUpdate = (nextProps, nextState) => {\n let wwjd =\n Math.abs(nextProps.size.width - this.props.size.width) > 10 ||\n Math.abs(nextProps.size.height - this.props.size.height) > 10\n if (\n wwjd ||\n nextState.zoom !== this.state.zoom ||\n this.state.activeMarker !== nextState.activeMarker ||\n this.state.selectedMarker !== nextState.selectedMarker ||\n this.state.itemsHover !== nextState.itemsHover\n ) {\n return true\n } else {\n return false\n }\n }\n\n render() {\n let sizes = {}\n\n if (!this.props.size.height || !this.props.size.width) {\n sizes = {\n height: '100%',\n width: '100%',\n }\n } else {\n sizes = {\n height: `${this.props.size.height}px`,\n width: `${this.props.size.width * 0.8}px`,\n }\n }\n\n return (\n \n
\n {this.getLegend()}\n \n \n \n {this.renderGeography}\n \n {this.getMarkers()}\n \n \n
\n
\n \n \n
\n
\n )\n }\n}\n\nRegionsMap.defaultProps = {\n onClickRegion: () => {},\n}\n\nRegionsMap.propTypes = {\n onClickRegion: PropTypes.func.isRequired,\n regionCounts: PropTypes.objectOf(PropTypes.number),\n theme: themeShape,\n size: PropTypes.shape({\n width: PropTypes.number,\n height: PropTypes.number,\n }),\n}\n\nexport default themeable(RegionsMap)\n","/**\n *\n * RegionsWidget\n *\n */\n\nimport React from 'react'\nimport PropTypes from 'prop-types'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport _ from 'lodash'\nimport { List } from 'immutable'\n\nimport messages from './messages'\nimport { TopTitle, CardBody } from 'components/Card'\nimport WidgetCard from 'components/WidgetCard'\nimport gql from 'graphql-tag'\nimport { Query } from 'react-apollo'\nimport { queryHasPivotFilter } from 'query-builder'\nimport SizeMe from 'components/SizeMe'\nimport { getSearchIdForSonraiSearches } from 'utils/sonraiUtils'\nimport { Map } from 'immutable'\nimport {\n hasSonraiSearch,\n getSonraiSearchQuery,\n getSonraiSearchData,\n getBaseSonraiSearchData,\n getFields,\n getSearchCard,\n getSelection,\n} from 'query-builder'\nimport RegionsMap from './RegionsMap'\nimport { FormattedMessage } from 'react-intl'\nimport TextLink from 'components/TextLink'\nconst styles = {\n title: {\n fontSize: '22px',\n fontWeight: '300',\n marginBottom: '5px',\n },\n subtitle: {\n fontSize: '14px',\n fontWeight: '300',\n color: '#777777',\n },\n}\n\nconst SizedRegionsMap = SizeMe(RegionsMap)\n\nclass RegionsWidget extends React.Component {\n constructor(props) {\n super(props)\n this.state = {\n thisHeight: null,\n thisWidth: null,\n }\n }\n\n getTableSavedSearchQuery = () => {\n let fields = getFields(\n this.props.savedSearches,\n this.props.resultLayout.indexedSearches.search\n )\n\n if (fields.isEmpty()) {\n return {}\n }\n\n const rootField = fields.find(\n statement => !statement.get('parentId'),\n null,\n Map()\n )\n\n const queryBuilder = this.props.getQueryBuilder(fields)\n queryBuilder.enableFlattenMode()\n queryBuilder.skipCounts()\n\n //Force-add the required columns if they are not present\n const searchCard = getSearchCard(this.props.resultLayout, 'search')\n\n queryBuilder.fields = queryBuilder.fields.update(searchCard.id, field => {\n if (!field.get('displayFields', List()).includes('region')) {\n field = field.update('displayFields', List(), propList =>\n propList.push('region')\n )\n }\n\n return field\n })\n\n const query = queryBuilder.buildPivotableSource(\n rootField.get('id'),\n 'regionmapwidget',\n { limit: 1000 }\n )\n\n return { ...query, flatten: true }\n }\n\n getWidgetQueryConfig = () => {\n if (hasSonraiSearch(this.props.options, 'search')) {\n return getSonraiSearchQuery(this.props.options.sonraiSearches.search)\n } else {\n return this.getTableSavedSearchQuery()\n }\n }\n\n getRootItem = data => {\n const keys = Object.keys(data)\n const rootKey = keys[0]\n\n return data[rootKey]\n }\n\n onClickSearch = () => {\n this.props.onClickSearch({\n savedSearchId: this.props.resultLayout.indexedSearches.search,\n sonraiSearchName: this.props.options.sonraiSearches.search,\n searchTitle: this.props.title,\n })\n }\n\n getSearchName = () => {\n return hasSonraiSearch(this.props.options, 'search')\n ? this.props.options.sonraiSearches.search\n : ''\n }\n\n onClickSonraiSearchRegion = (allData, regionName) => {\n const baseData = getBaseSonraiSearchData(allData)\n const dataType = Object.keys(baseData)[0]\n const baseQuery = this.props.sonraiSearches.find(\n search => search.get('name') === this.props.options.sonraiSearches.search\n )\n\n if (!baseQuery) {\n return\n }\n\n const baseQueryContent = baseQuery.get('query')\n\n let finalQuery\n\n const hasWhereClauseRegex = new RegExp(\n `${dataType}\\\\s*\\\\(.*where\\\\s*:\\\\s*(\\\\{.*\\\\})\\\\).*\\\\{`\n )\n const whereMatch = baseQueryContent.match(hasWhereClauseRegex)\n if (whereMatch && whereMatch.length > 1) {\n //The sonrai search has a built-in where clause, so we want to preserve that\n const originalWhereClause = whereMatch[1]\n finalQuery = `\n {\n ${dataType} ( where: {\n and: [\n {region: {op: EQ, value: \"${regionName}\"}},\n ${originalWhereClause}\n ]\n }) {\n items {\n name\n region\n srn\n }\n }\n }\n `\n } else {\n finalQuery = `\n {\n ${dataType} ( where: {\n region: {op: EQ, value: \"${regionName}\"}\n }) {\n items {\n name\n region\n srn\n }\n }\n }\n `\n }\n\n this.props.onClickSearch({\n advancedQuery: finalQuery,\n sonraiSearchName: this.props.options.sonraiSearches.search,\n searchTitle: `${this.props.title} - ${regionName}`,\n })\n }\n\n onClickRegion = (allData, regionName) => {\n if (!regionName || !this.props.onClickSearch) {\n return\n }\n\n if (hasSonraiSearch(this.props.options, 'search')) {\n this.onClickSonraiSearchRegion(allData, regionName)\n } else {\n const filter = {\n label: 'region',\n value: regionName,\n }\n\n this.props.onClickSearch({\n savedSearchId: this.props.resultLayout.indexedSearches.search,\n searchTitle: this.props.title,\n filter: filter,\n })\n }\n }\n\n getUsedRegionInfo = data => {\n if (_.isEmpty(data)) {\n return {}\n }\n\n if (hasSonraiSearch(this.props.options, 'search')) {\n //This widget expects the results to be a shape like:\n // {\n // \"data\": {\n // \"Data\": {\n // \"group\": [\n // {\n // \"key\": {\n // \"region\": \"eu-west-2\"\n // },\n // \"count\": 7\n // },\n // {\n // \"key\": {\n // \"region\": \"eastus\"\n // },\n // \"count\": 1\n // }\n // ]\n // }\n // }\n // }\n\n const rootData = getSonraiSearchData(data)\n const groupResults = rootData.group || []\n const formatted = {}\n\n groupResults.forEach(result => {\n const regionName = _.get(result, ['key', 'region'], 'UNMAPPED')\n formatted[regionName] = result.count\n })\n\n return formatted\n } else {\n const fields = getFields(\n this.props.savedSearches,\n this.props.resultLayout.indexedSearches.search\n ).toJS()\n\n const searchCard = getSearchCard(this.props.resultLayout, 'search')\n const selection = getSelection(fields, searchCard, 'items', 'items')\n\n if (!selection) {\n return 'DATA ERROR'\n }\n\n const items = _.get(data, [...selection])\n const counts = _.countBy(items, 'region')\n return counts\n }\n }\n\n handleSizeChange = _.throttle(size => {\n this.setState({ thisHeight: size.height })\n\n this.setState({ thisWidth: size.width })\n }, 100)\n\n getSearchId = () => {\n const { options, sonraiSearches, resultLayout } = this.props\n let searchObj = Map({ uiSearch: null, advancedSearch: null })\n if (hasSonraiSearch(options, 'search')) {\n const searches = getSearchIdForSonraiSearches(options, sonraiSearches)\n searchObj = searchObj.set('advancedSearch', searches)\n } else {\n searchObj = searchObj.set('uiSearch', resultLayout.indexedSearches.search)\n }\n return searchObj\n }\n\n render() {\n if (this.props.data === undefined) {\n const queryConfig = this.getWidgetQueryConfig()\n const searchId = this.getSearchId()\n\n if (queryConfig) {\n if (!queryConfig.gqlStatement) {\n return (\n \n )\n }\n\n const queryObj = gql`\n ${queryConfig.gqlStatement}\n `\n\n const filtered = queryHasPivotFilter(queryConfig.gqlStatement)\n return (\n \n {({ error, data, refetch, networkStatus }) => {\n const regionCounts = this.getUsedRegionInfo(data)\n return (\n \n \n \n {this.props.title}\n \n {this.props.subtitle}
\n \n \n {networkStatus !== 1 && !error && (\n \n this.onClickRegion(data, clickedRegionName)\n }\n />\n )}\n \n \n )\n }}\n \n )\n } else {\n return (\n \n \n {this.props.title}
\n {this.props.subtitle}
\n \n \n \n {' '}\n
\n \n \n )\n }\n } else {\n const regionCounts = this.props.data\n return (\n \n \n {this.props.title}\n \n \n \n this.onClickRegion(this.props.data, clickedRegionName)\n }\n />\n \n \n )\n }\n }\n}\n\nRegionsWidget.propTypes = {\n allowDelete: PropTypes.bool,\n allowUpdate: PropTypes.bool,\n data: PropTypes.array,\n getQueryBuilder: PropTypes.func,\n resultLayout: PropTypes.object,\n onClickSearch: PropTypes.func,\n onRemove: PropTypes.func,\n options: PropTypes.object,\n toggleStatic: PropTypes.func,\n savedSearches: ImmutablePropTypes.map.isRequired,\n sonraiSearches: ImmutablePropTypes.list,\n static: PropTypes.bool,\n title: PropTypes.string,\n subtitle: PropTypes.string,\n onEdit: PropTypes.func,\n widget: PropTypes.object,\n}\n\nexport default RegionsWidget\n","import React from 'react'\nimport PropTypes from 'prop-types'\nimport { scaleLinear } from 'd3-scale'\nimport ContainerDimensions from 'react-container-dimensions'\nimport Button from 'components/Button'\nimport geoObj from 'assets/world-50m.json'\nimport Icon from 'components/Icon'\nimport {\n ComposableMap,\n ZoomableGroup,\n Geographies,\n Geography,\n Lines,\n Line,\n} from 'react-simple-maps'\nimport themeable, { themeShape } from 'containers/ThemeManager/Themeable'\n\nclass ArcMap extends React.Component {\n constructor(props) {\n super(props)\n\n this.state = {\n zoom: 1.5,\n activeMarker: null,\n }\n\n this.styles = {\n zoomButton: {\n color: 'white',\n padding: '0 6px',\n },\n arcStyle: {\n stroke: this.props.theme.primary,\n fill: this.props.theme.primary,\n opacity: 0.8,\n strokeWidth: 1,\n },\n }\n }\n\n handleZoomIn = () => {\n this.setState(currentState => ({\n zoom: currentState.zoom * 1.25,\n }))\n }\n\n handleZoomOut = () => {\n this.setState(currentState => ({\n zoom: currentState.zoom / 1.25,\n }))\n }\n\n renderGeography = (geographies, projection) => {\n const baseStyle = {\n fill: this.props.theme.neutralLight,\n stroke: this.props.theme.neutralMedium,\n strokeWidth: 0.5,\n outline: 'none',\n }\n\n return geographies\n .filter(geo => geo.properties.NAME !== 'Antarctica')\n .map((geography, i) => (\n \n ))\n }\n\n // This funtion returns a curve command that builds a quadratic curve.\n // And depending on the line's curveStyle property it curves in one direction or the other.\n buildCurves = (scale, start, end, line) => {\n const x0 = start[0]\n const x1 = end[0]\n const y0 = start[1]\n const y1 = end[1]\n\n const dy = y1 - y0\n\n const goesUp = dy > 0\n\n const curve = {\n pointOfControlUp: `${x1} ${y0}`,\n pointOfControlDown: `${x0} ${y1}`,\n }[goesUp ? 'pointOfControlUp' : 'pointOfControlDown']\n\n const thickness = scale(line.count) / 2\n\n const innerCurve = {\n pointOfControlUp: `${x1} ${y0 + thickness}`,\n pointOfControlDown: `${x0} ${y1 - thickness}`,\n }[goesUp ? 'pointOfControlUp' : 'pointOfControlDown']\n\n return `\n M ${x0 + thickness} ${y0 + thickness}\n Q ${curve} ${x1} ${y1}\n L ${x1} ${y1}\n Q ${innerCurve} ${x0} ${y0}\n L ${x0 + thickness} ${y0 + thickness}\n `\n }\n\n getGradient = (conf, index) => {\n const x0 = conf.from.coordinates[0]\n const x1 = conf.to.coordinates[0]\n const dx = x1 - x0\n\n return (\n 0 ? 'rotate(90)' : undefined}\n >\n 0 ? this.props.theme.primary : this.props.theme.emphasis\n }\n />\n 0 ? this.props.theme.emphasis : this.props.theme.primary\n }\n />\n \n )\n }\n\n getArcs = () => {\n const arcsWithCount = {}\n this.props.arcs.forEach(config => {\n const from = config.from.coordinates.join('-')\n const to = config.to.coordinates.join('-')\n if (from === '-' || to === '-') {\n return\n }\n\n const key = `${from},${to}`\n\n if (!arcsWithCount[key]) {\n arcsWithCount[key] = { ...config, count: 1 }\n } else {\n arcsWithCount[key].count = arcsWithCount[key].count + 1\n }\n })\n\n return Object.values(arcsWithCount)\n }\n\n render() {\n const arcs = this.props.combine ? this.getArcs() : this.props.arcs\n let scale\n\n if (this.props.combine && arcs.length > 0) {\n const counts = arcs.sort((a, b) => b.count - a.count)\n const maxLocation = counts[0]\n\n scale = scaleLinear()\n .domain([0, maxLocation.count])\n .range([1, 10])\n\n scale.clamp(true)\n }\n\n return (\n \n
\n \n this.getGradient(conf, index))}\n >\n \n \n {this.renderGeography}\n \n\n \n {arcs.map((conf, index) => (\n \n ))}\n \n \n \n \n
\n
\n \n \n
\n
\n )\n }\n}\n\nArcMap.defaultProps = {\n arcs: [],\n combine: true,\n onClickRegion: () => {},\n}\n\nArcMap.propTypes = {\n arcs: PropTypes.array,\n combine: PropTypes.bool,\n theme: themeShape,\n}\n\nexport default themeable(ArcMap)\n","import React from 'react'\nimport PropTypes from 'prop-types'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport { Query } from 'react-apollo'\nimport gql from 'graphql-tag'\nimport _ from 'lodash'\nimport { Map, List } from 'immutable'\n\nimport { TopTitle, CardBody } from 'components/Card'\nimport WidgetCard from 'components/WidgetCard'\nimport { getCoordConf } from 'utils/widgetUtils'\nimport { getSearchIdForSonraiSearches } from 'utils/sonraiUtils'\nimport TextLink from 'components/TextLink'\n\nimport {\n getSonraiSearchQuery,\n getSonraiSearchData,\n hasSonraiSearch,\n queryHasPivotFilter,\n getFields,\n getSelection,\n} from 'query-builder'\nimport ArcMap from './ArcMap'\n\nexport class ArcMapWidget extends React.Component {\n styles = {\n title: {\n fontSize: '22px',\n fontWeight: '300',\n },\n }\n\n getText = locations => {\n if (_.isEmpty(locations)) {\n return []\n }\n\n const points = []\n const locs = new Set() //eslint-disable-line no-restricted-globals\n locations.items.forEach(item => {\n const name = _.get(item, ['isInRegion', 'items', 0, 'name'])\n locs.add(name)\n })\n locs.forEach(name => {\n const { coordinates } = getCoordConf(name)\n const nme = name || 'null'\n if (\n coordinates &&\n coordinates[0] !== null &&\n coordinates[1] !== null &&\n nme !== null\n ) {\n points.push({ position: coordinates, name: nme })\n }\n })\n\n return points\n }\n\n getArcs = locations => {\n if (_.isEmpty(locations)) {\n return []\n }\n\n const arcs = []\n\n locations.items.forEach(item => {\n const name = item.region\n const { coordinates } = getCoordConf(name)\n if (coordinates !== null && coordinates.indexOf(null) === -1) {\n const attachedLocations = _.get(item, ['performedAt', 'items'])\n attachedLocations.forEach(loc => {\n arcs.push({\n from: {\n name: loc.srn,\n coordinates: [loc.longitude, loc.latitude],\n },\n to: {\n name,\n coordinates,\n },\n })\n })\n }\n })\n\n return arcs\n }\n\n getTableSavedSearchQuery = () => {\n let fields = getFields(\n this.props.savedSearches,\n this.props.resultLayout.indexedSearches.advmapsearch\n )\n\n if (fields.isEmpty()) {\n return {}\n }\n\n const rootField = fields.find(\n statement => !statement.get('parentId'),\n null,\n Map()\n )\n\n const queryBuilder = this.props.getQueryBuilder(fields)\n queryBuilder.enableFlattenMode()\n queryBuilder.skipCounts()\n\n //Force-add the required columns if they are not present\n queryBuilder.fields = queryBuilder.fields.map(field => {\n const nodeType = field.getIn(['definition', 'type', 'name'])\n if (nodeType === 'LocationEdgeRelation') {\n if (!field.get('displayFields', List()).includes('latitude')) {\n field = field.update('displayFields', List(), propList =>\n propList.push('latitude')\n )\n }\n\n if (!field.get('displayFields', List()).includes('longitude')) {\n field = field.update('displayFields', List(), propList =>\n propList.push('longitude')\n )\n }\n } else if (nodeType === 'ActionEdgeRelation') {\n if (!field.get('displayFields', List()).includes('region')) {\n field = field.update('displayFields', List(), propList =>\n propList.push('region')\n )\n }\n }\n\n return field\n })\n\n const query = queryBuilder.buildPivotableSource(\n rootField.get('id'),\n 'arcmapQuery',\n { limit: 500 }\n )\n\n return {\n ...query,\n flatten: true,\n }\n }\n\n getQuery = () => {\n if (hasSonraiSearch(this.props.options, 'advmapsearch')) {\n return getSonraiSearchQuery(\n this.props.options.sonraiSearches.advmapsearch\n )\n } else {\n return this.getTableSavedSearchQuery()\n }\n }\n\n getSearchName = () => {\n return hasSonraiSearch(this.props.options, 'advmapsearch')\n ? this.props.options.sonraiSearches.advmapsearch\n : this.props.savedSearches.getIn([\n this.props.resultLayout.indexedSearches.advmapsearch,\n 'name',\n ])\n }\n\n onClickSearch = () => {\n this.props.onClickSearch({\n savedSearchId: this.props.resultLayout.indexedSearches.advmapsearch,\n sonraiSearchName: this.props.options.sonraiSearches.advmapsearch,\n searchTitle: this.props.title,\n })\n }\n\n getData = data => {\n if (!_.isEmpty(this.props.options.sonraiSearches.advmapsearch)) {\n return getSonraiSearchData(data)\n } else {\n return this.getSavedSearchData(data)\n }\n }\n\n getSavedSearchData = data => {\n const fields = getFields(\n this.props.savedSearches,\n this.props.resultLayout.indexedSearches.advmapsearch\n )\n\n const hasPerformedAts = fields\n .filter(\n field => field.getIn(['definition', 'name'], '') === 'performedAt'\n )\n .map(field => field.get('parentId'))\n .toList()\n\n const actions = hasPerformedAts\n .map(id => fields.get(id))\n .filter(field => field.getIn(['definition', 'name'], '') === 'Actions')\n\n if (actions.isEmpty()) {\n return []\n }\n\n const selection = getSelection(\n fields.toJS(),\n actions.first().toJS(),\n undefined,\n 'items'\n )\n\n if (!selection) {\n return []\n }\n\n return _.get(data, [...selection], [])\n }\n\n getSearchId = () => {\n const { options } = this.props\n let searchObj = Map({ uiSearch: null, advancedSearch: null })\n if (hasSonraiSearch(options, 'advmapsearch')) {\n const searches = getSearchIdForSonraiSearches(\n options,\n this.props.sonraiSearches\n )\n searchObj = searchObj.set('advancedSearch', searches)\n } else {\n searchObj = searchObj.set(\n 'uiSearch',\n this.props.resultLayout.indexedSearches.advmapsearch\n )\n }\n\n return searchObj\n }\n\n render() {\n if (this.props.data === undefined) {\n const query = this.getQuery()\n const searchId = this.getSearchId()\n if (!query.gqlStatement) {\n return \n }\n\n const filtered = queryHasPivotFilter(query.gqlStatement)\n\n return (\n \n {({ error, data, refetch, networkStatus }) => {\n const results = this.getData(data)\n return (\n \n \n \n {this.props.title}\n \n \n \n \n \n \n )\n }}\n \n )\n } else {\n const { arcs, text } = this.props.data\n return (\n \n {' '}\n \n {this.props.title}\n \n \n \n \n \n )\n }\n }\n}\n\nArcMapWidget.propTypes = {\n allowDelete: PropTypes.bool,\n allowUpdate: PropTypes.bool,\n getQueryBuilder: PropTypes.func.isRequired,\n title: PropTypes.string.isRequired,\n onClickSearch: PropTypes.func,\n onRemove: PropTypes.func,\n toggleStatic: PropTypes.func,\n static: PropTypes.bool,\n options: PropTypes.object,\n resultLayout: PropTypes.object,\n savedSearches: ImmutablePropTypes.map.isRequired,\n sonraiSearches: ImmutablePropTypes.iterable.isRequired,\n onEdit: PropTypes.func,\n data: PropTypes.object,\n widget: PropTypes.object,\n}\n\nexport default ArcMapWidget\n","import React, { Component } from 'react'\nimport PropTypes from 'prop-types'\nimport WidgetCard from 'components/WidgetCard'\nimport MarkdownDisplay from 'components/MarkdownDisplay'\n\nclass TextWidget extends Component {\n styles = {\n title: {\n fontWeight: '400',\n fontSize: '1.15rem',\n overflow: 'hidden',\n marginBottom: '0.5rem',\n },\n description: {\n overflow: 'hidden',\n },\n }\n\n render() {\n const {\n title,\n resultLayout: {\n widgetOptions: { description },\n },\n } = this.props\n return (\n \n {title}
\n \n \n
\n \n )\n }\n}\n\nTextWidget.propTypes = {\n resultLayout: PropTypes.shape({\n widgetOptions: PropTypes.shape({\n description: PropTypes.string,\n }),\n }),\n title: PropTypes.string,\n allowDelete: PropTypes.bool,\n allowUpdate: PropTypes.bool,\n onRemove: PropTypes.func,\n onEdit: PropTypes.func,\n static: PropTypes.bool,\n widget: PropTypes.object,\n}\n\nexport default TextWidget\n","import { defineMessages } from 'react-intl'\n\nexport default defineMessages({\n widgetError: {\n id: 'app.containers.SolutionCenterWidgets.widgetError',\n defaultMessage: 'Oops...There was an error loading your widget data',\n },\n widgetNoPermission: {\n id: 'app.containers.SolutionCenterWidgets.widgetNoPermission',\n defaultMessage: 'Insufficent Permissions to view data',\n },\n logError: {\n id: 'app.containers.SolutionCenterWidgets.logError',\n defaultMessage: 'Click to Log',\n },\n})\n","import React, { Component } from 'react'\nimport PropTypes from 'prop-types'\nimport { FormattedMessage } from 'react-intl'\n\nimport WidgetCard from 'components/WidgetCard'\nimport fail from 'assets/sadcloud.png'\n\nimport messages from './messages'\n\nexport default class WidgetErrorBoundry extends Component {\n constructor(props) {\n super(props)\n this.state = {\n hasError: false,\n errorMessage: '',\n errorSource: '',\n componentStack: '',\n errorStack: '',\n }\n\n this.styles = {\n errorWrapper: {\n overflow: 'auto',\n color: '#666666',\n textAlign: 'center',\n },\n errorImg: {\n height: '40px',\n maxWidth: '150px',\n },\n }\n }\n\n componentDidCatch(error, info) {\n this.setState({\n hasError: true,\n errorMessage: error.message,\n errorStack: error.stack,\n componentStack: info.componentStack,\n errorSource: error.source ? error.source.body : '',\n })\n }\n\n render() {\n if (this.state.hasError || !this.props.canViewData) {\n return (\n \n \n {this.props.canViewData && (\n

\n )}\n
\n {!this.props.canViewData ? (\n `${messages.widgetNoPermission.defaultMessage} for Widget: ${\n this.props.title\n }`\n ) : (\n \n )}\n
\n
{this.state.errorMessage}
\n
\n \n )\n }\n\n return this.props.children\n }\n}\n\nWidgetErrorBoundry.defaultProps = {\n allowDelete: true,\n allowUpdate: false,\n}\n\nWidgetErrorBoundry.propTypes = {\n allowDelete: PropTypes.bool,\n allowUpdate: PropTypes.bool,\n onEdit: PropTypes.func,\n onRemove: PropTypes.func,\n children: PropTypes.oneOfType([\n PropTypes.object,\n PropTypes.arrayOf(PropTypes.object),\n ]).isRequired,\n title: PropTypes.string,\n canViewData: PropTypes.bool,\n}\n","import React from 'react'\nimport PropTypes from 'prop-types'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport { connect } from 'react-redux'\nimport { createStructuredSelector } from 'reselect'\nimport { compose, bindActionCreators } from 'redux'\nimport permissionChecker from 'containers/PermissionChecker'\nimport {\n selectSavedSearches,\n selectSonraiSearches,\n selectPivot,\n} from 'containers/SonraiData/selectors'\nimport { WIDGET_TYPES } from 'appConstants'\nimport BigStatWidget from 'components/BigStatWidget'\nimport TableWidget from 'components/TableWidget'\nimport RatioWidget from 'components/RatioWidget'\nimport GaugeWidget from 'components/GaugeWidget'\nimport RegionsWidget from 'components/RegionsWidget'\nimport ListWidget from 'components/ListWidget'\nimport PieChartWidget from 'components/PieChartWidget'\nimport BarChartWidget from 'components/BarChartWidget'\nimport ArcMapWidget from 'components/ArcMapWidget'\nimport AlertWidget from 'components/AlertWidget'\nimport SparkWidget from 'components/SparkWidget'\nimport ComplianceWidget from 'components/ComplianceWidget'\nimport TextWidget from 'components/TextWidget'\nimport WidgetErrorBoundry from './WidgetErrorBoundry'\nimport _ from 'lodash'\n\nexport class Widget extends React.Component {\n getWidgetComponent = widgetType => {\n switch (widgetType) {\n case WIDGET_TYPES.LIST:\n return ListWidget\n case WIDGET_TYPES.BIG_COUNT:\n return BigStatWidget\n case WIDGET_TYPES.TABLE:\n return TableWidget\n case WIDGET_TYPES.RATIO:\n return RatioWidget\n case WIDGET_TYPES.GAUGE:\n return GaugeWidget\n case WIDGET_TYPES.REGIONS:\n return RegionsWidget\n case WIDGET_TYPES.PIE_CHART:\n return PieChartWidget\n case WIDGET_TYPES.BAR_CHART:\n return BarChartWidget\n case WIDGET_TYPES.ADVMAP:\n return ArcMapWidget\n case WIDGET_TYPES.ALERT:\n return AlertWidget\n case WIDGET_TYPES.SPARK:\n return SparkWidget\n case WIDGET_TYPES.COMPLIANCE:\n return ComplianceWidget\n case WIDGET_TYPES.TEXT:\n return TextWidget\n default:\n return null\n }\n }\n\n removeWidget = () => {\n this.props.removeWidget(this.props.widget.get('srn'))\n }\n\n editWidget = () => {\n this.props.onEdit(this.props.widget.get('srn'))\n }\n\n toggleStatic = () => {\n this.props.toggleStatic(this.props.widget.get('srn'))\n }\n\n getWidgetResultsTitle = (searchTitle, sonraiSearchName) => {\n const { widget } = this.props\n let widgetObj = widget.toJS()\n\n if (\n widgetObj.resultLayout &&\n widgetObj.resultLayout.indexedSearches &&\n widgetObj.resultLayout.indexedSearches\n ) {\n const key = _.keys(widgetObj.resultLayout.indexedSearches)\n const id = widgetObj.resultLayout.indexedSearches[key]\n const search = this.props.savedSearches.get(id)\n if (search && !sonraiSearchName) {\n return search.get('name')\n }\n }\n\n return searchTitle || sonraiSearchName\n }\n\n setWidgetOptions = options => {\n this.props.setWidgetOptions(this.props.widget.get('srn'), options)\n }\n\n shouldComponentUpdate(nextProps) {\n let render = nextProps.canRender === false ? false : true\n return render\n }\n\n render() {\n const WidgetComponent = this.getWidgetComponent(\n this.props.widget.get('type')\n )\n\n if (!WidgetComponent) {\n return \n }\n const canEdit = this.props.userHasPermission({\n permissionName: 'edit.solutioncards',\n resourceId: this.props.widget.get('resourceId'),\n })\n\n const canViewData = this.props.canViewData\n\n return (\n \n \n \n )\n }\n}\n\nWidget.propTypes = {\n getQueryBuilder: PropTypes.func.isRequired,\n layout: PropTypes.object.isRequired,\n onClickAlert: PropTypes.func.isRequired,\n onClickControlFramework: PropTypes.func.isRequired,\n onClickNodeView: PropTypes.func.isRequired,\n onClickSearch: PropTypes.func.isRequired,\n pivot: ImmutablePropTypes.map.isRequired,\n removeWidget: PropTypes.func.isRequired,\n savedSearches: ImmutablePropTypes.iterable.isRequired,\n onEdit: PropTypes.func.isRequired,\n setWidgetOptions: PropTypes.func.isRequired,\n sonraiSearches: ImmutablePropTypes.iterable.isRequired,\n toggleStatic: PropTypes.func.isRequired,\n userHasPermission: PropTypes.func.isRequired,\n widget: ImmutablePropTypes.map.isRequired,\n canRender: PropTypes.bool,\n canViewData: PropTypes.bool,\n}\nconst mapStateToProps = createStructuredSelector({\n pivot: selectPivot,\n savedSearches: selectSavedSearches,\n sonraiSearches: selectSonraiSearches,\n})\n\nfunction mapDispatchToProps(dispatch) {\n return bindActionCreators({}, dispatch)\n}\n\nconst withConnect = connect(mapStateToProps, mapDispatchToProps)\n\nexport default compose(withConnect, permissionChecker)(Widget)\n","import React, { Fragment } from 'react'\nimport PropTypes from 'prop-types'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport { connect } from 'react-redux'\nimport { createStructuredSelector } from 'reselect'\nimport { compose, bindActionCreators } from 'redux'\nimport injectReducer from 'utils/injectReducer'\nimport GridLayout from 'react-grid-layout'\nimport _ from 'lodash'\nimport qs from 'query-string'\nimport { push } from 'connected-react-router'\nimport { List, Map } from 'immutable'\n\nimport CenterContent from 'components/CenterContent'\nimport themeable, { themeShape } from 'containers/ThemeManager/Themeable'\nimport Icon from 'components/Icon'\nimport BorderlessButton from 'components/BorderlessButton'\nimport BorderedCard from 'components/BorderedCard'\nimport SizeMe from 'components/SizeMe'\nimport { getNodeViewPushParams } from 'utils/sonraiUtils'\nimport {\n setWidgetOptions,\n removeSCWidget,\n toggleSCWidgetStatic,\n updateWidget,\n setShowCreateWidgetModal,\n} from 'containers/SolutionCenter/actions'\nimport { selectQueryTypes, selectPivot } from 'containers/SonraiData/selectors'\nimport { QueryBuilder } from 'query-builder'\nimport WidgetModal from 'containers/WidgetModal'\nimport permissionsChecker from 'containers/PermissionChecker'\n\nimport reducer from './reducer'\nimport { selectLayout, selectSavingWidget } from './selectors'\nimport Widget from './Widget'\n\nconst SizedGridLayout = SizeMe(GridLayout)\n\nexport class SolutionCenterWidgets extends React.Component {\n state = {\n showWidgetModal: false,\n editingWidgetId: null,\n renderLocks: Map(),\n }\n\n componentDidUpdate(oldProps) {\n if (oldProps.savingWidget && !this.props.savingWidget) {\n this.setState({\n showWidgetModal: false,\n editingWidgetId: null,\n })\n }\n }\n\n setEditingWidget = widgetId => {\n this.setState({\n showWidgetModal: true,\n editingWidgetId: widgetId,\n })\n }\n\n toggleEditWidgetModal = () => {\n this.setState(currentState => ({\n showWidgetModal: !currentState.showWidgetModal,\n editingWidgetId: null,\n }))\n }\n\n setWidgetOptions = (widgetId, options) => {\n this.props.setWidgetOptions(this.props.card.get('srn'), widgetId, options)\n }\n\n onClickSearch = params => {\n const pathname = '/App/WidgetResultExplorer/'\n this.props.push({\n pathname: pathname,\n state: params,\n })\n }\n\n onClickAlert = id => {\n this.props.push({\n pathname: '/App/Alert',\n search: qs.stringify({\n alertId: id,\n }),\n })\n }\n\n onClickControlFramework = srn => {\n this.props.push({\n pathname: '/App/ControlCenter/ControlGroup',\n search: qs.stringify({\n controlGroupId: srn,\n }),\n })\n }\n\n removeWidget = widgetId => {\n this.props.removeSCWidget(this.props.card.get('srn'), widgetId)\n }\n\n toggleStatic = widgetId => {\n this.props.toggleSCWidgetStatic(this.props.card.get('srn'), widgetId)\n }\n\n onClickNodeView = (nodeId, type) => {\n this.props.push(getNodeViewPushParams(nodeId, type))\n }\n\n handleLayoutChange = newLayout => {\n newLayout.forEach(widgetLayout => {\n const oldWidgetLayout =\n _.find(this.props.layout, layout => layout.i === widgetLayout.i) || {}\n\n const oldProps = {\n h: oldWidgetLayout.h,\n w: oldWidgetLayout.w,\n x: oldWidgetLayout.x,\n y: oldWidgetLayout.y,\n }\n\n const newProps = {\n h: widgetLayout.h,\n w: widgetLayout.w,\n x: widgetLayout.x,\n y: widgetLayout.y,\n }\n\n if (!_.isEqual(oldProps, newProps)) {\n const widget = this.props.card\n .get('widgets')\n .toJS()\n .find(widget => widget.sid === widgetLayout.i)\n\n let updatedWidget = {\n ...widget,\n }\n\n updatedWidget.widgetSize = [widgetLayout.w, widgetLayout.h]\n updatedWidget.widgetLocation = [widgetLayout.x, widgetLayout.y]\n this.props.updateWidget(this.props.card.get('srn'), updatedWidget)\n }\n })\n }\n\n getQueryBuilder = fields => {\n const queryBuilder = new QueryBuilder({\n query: fields,\n types: this.props.queryTypes,\n pivot: this.props.pivot,\n })\n\n return queryBuilder\n }\n\n lockRender = (layout, oldItem) => {\n this.setState(old => {\n return { renderLocks: old.renderLocks.set(oldItem.i, false) }\n })\n }\n\n unlockRender = (layout, oldItem) => {\n this.setState(old => {\n return { renderLocks: old.renderLocks.set(oldItem.i, true) }\n })\n }\n\n render() {\n const canUpdateSolutionCard = this.props.userHasPermission({\n permissionName: 'edit.solutioncards',\n resourceId: this.props.card.get('resourceId'),\n })\n\n const canViewData = this.props.userHasPermission({\n permissionName: 'view.data',\n })\n\n if (\n this.props.card.get('widgets', List()).isEmpty() &&\n canUpdateSolutionCard\n ) {\n return (\n \n
\n \n this.props.setShowCreateWidgetModal(true)}\n style={{ width: '100%', height: '100%' }}\n >\n \n \n
\n Create new Widget\n \n \n \n
\n )\n }\n\n const editingWidget = this.props.card\n .get('widgets', List())\n .find(\n widget => widget.get('srn') === this.state.editingWidgetId,\n null,\n Map()\n )\n return (\n \n {/*Setting useCSSTransforms makes widget dropdowns not be covered up by other widgets*/}\n \n {this.props.card.get('widgets', List()).map((widget, index) => {\n const canEdit = this.props.userHasPermission({\n permissionName: 'edit.solutioncards',\n resourceId: widget.get('resourceId'),\n })\n\n return (\n \n \n
\n )\n })}\n \n {canUpdateSolutionCard && (\n \n \n \n )}\n \n )\n }\n}\n\nSolutionCenterWidgets.propTypes = {\n card: ImmutablePropTypes.contains({\n sid: PropTypes.string.isRequired,\n srn: PropTypes.string.isRequired,\n widgets: ImmutablePropTypes.listOf(\n ImmutablePropTypes.contains({\n sid: PropTypes.string.isRequired,\n title: PropTypes.string.isRequired,\n subtitle: PropTypes.string.isRequired,\n type: PropTypes.oneOf([\n 'bigCount',\n 'table',\n 'pieChart',\n 'lineChart',\n 'ratio',\n 'barChart',\n 'list',\n 'gauge',\n 'map',\n 'regions',\n 'advMap',\n 'compliance',\n 'spark',\n 'alert',\n ]).isRequired,\n options: ImmutablePropTypes.map,\n query: ImmutablePropTypes.map,\n selection: PropTypes.string,\n formatter: PropTypes.func,\n widgetSize: ImmutablePropTypes.list.isRequired,\n widgetLocation: ImmutablePropTypes.list.isRequired,\n static: PropTypes.bool.isRequired,\n })\n ),\n }).isRequired,\n layout: PropTypes.array.isRequired,\n pivot: ImmutablePropTypes.map.isRequired,\n push: PropTypes.func.isRequired,\n queryTypes: ImmutablePropTypes.iterable.isRequired,\n removeSCWidget: PropTypes.func.isRequired,\n savingWidget: PropTypes.bool,\n setShowCreateWidgetModal: PropTypes.func.isRequired,\n setWidgetOptions: PropTypes.func.isRequired,\n theme: themeShape,\n toggleSCWidgetStatic: PropTypes.func.isRequired,\n updateWidget: PropTypes.func.isRequired,\n userHasPermission: PropTypes.func,\n}\n\nconst mapStateToProps = createStructuredSelector({\n layout: selectLayout,\n pivot: selectPivot,\n queryTypes: selectQueryTypes,\n savingWidget: selectSavingWidget,\n})\n\nfunction mapDispatchToProps(dispatch) {\n return bindActionCreators(\n {\n push,\n removeSCWidget,\n setWidgetOptions,\n toggleSCWidgetStatic,\n updateWidget,\n setShowCreateWidgetModal,\n },\n dispatch\n )\n}\n\nconst withConnect = connect(mapStateToProps, mapDispatchToProps)\nconst withReducer = injectReducer({ key: 'SolutionCenterWidgets', reducer })\n\nexport default compose(\n withReducer,\n withConnect,\n permissionsChecker,\n themeable\n)(SolutionCenterWidgets)\n","import React from 'react'\nimport PropTypes from 'prop-types'\nimport { Label } from 'reactstrap'\n\nimport { exists } from 'utils/sonraiUtils'\nimport themeable, { themeShape } from 'containers/ThemeManager/Themeable'\n\nexport class Filter extends React.Component {\n constructor(props) {\n super(props)\n\n this.styles = {\n container: {\n padding: '0 0.3em',\n margin: '1em 0',\n },\n selected: {\n borderLeft: `2px solid ${props.theme.secondary}`,\n },\n unSelected: {\n borderLeft: `2px solid ${props.theme.light}`,\n },\n }\n }\n\n render() {\n return (\n \n \n {this.props.children}\n
\n )\n }\n}\n\nFilter.propTypes = {\n children: PropTypes.node,\n selectedValue: PropTypes.any,\n label: PropTypes.node,\n theme: themeShape,\n}\n\nexport default themeable(Filter)\n","import React from 'react'\nimport PropTypes from 'prop-types'\nimport AsyncSelect from 'react-select/async-creatable'\n\nexport class TypeaheadFilter extends React.Component {\n componentDidUpdate() {\n if (this.optionsLoaded() && this.callback) {\n this.callback(this.props.options)\n this.callback = null\n }\n }\n\n onChange = value => {\n if (!value || value.length === 0) {\n this.props.onChange(null)\n return\n }\n\n this.props.onChange({\n value: value.map(valObj => valObj.value),\n })\n }\n\n onCreateOption = value => {\n this.props.onChange({\n value: value,\n })\n\n this.props.addNew(value)\n }\n\n getSelectedValues = () => {\n if (!this.props.value || this.props.value.length === 0) {\n return null\n }\n\n return this.props.value.map(val => ({\n label: val,\n value: val,\n }))\n }\n\n optionsLoaded = () => {\n return this.props.options && this.props.options.length > 0\n }\n\n loadOptions = (inputValue, callback) => {\n if (this.props.loadOptions) {\n if (!this.optionsLoaded()) {\n this.props.loadOptions()\n this.callback = callback\n return\n }\n }\n\n callback(this.getFilteredOptions(inputValue))\n }\n\n getFilteredOptions = inputValue => {\n const { options } = this.props\n if (inputValue) {\n return options.filter(option =>\n option.label.toLowerCase().includes(inputValue.toLowerCase())\n )\n }\n return options\n }\n\n formatCreateLabel = inputValue => {\n return `\"${inputValue}\"`\n }\n\n render() {\n return (\n \n )\n }\n}\n\nTypeaheadFilter.defaultProps = {\n style: {},\n}\n\nTypeaheadFilter.propTypes = {\n addNew: PropTypes.func,\n onChange: PropTypes.func.isRequired,\n name: PropTypes.string,\n value: PropTypes.arrayOf(PropTypes.string),\n options: PropTypes.arrayOf(\n PropTypes.shape({\n label: PropTypes.string,\n value: PropTypes.any,\n })\n ),\n loadOptions: PropTypes.func,\n}\n\nexport default TypeaheadFilter\n","/**\n *\n * FilterPanel\n *\n */\n\nimport React from 'react'\nimport PropTypes from 'prop-types'\n\nimport themeable, { themeShape } from 'containers/ThemeManager/Themeable'\nimport Button from 'components/Button'\nimport BorderlessButton from 'components/BorderlessButton'\nimport Icon from 'components/Icon'\nimport IHelp from 'containers/IHelp'\n\nclass FilterPanel extends React.Component {\n constructor(props) {\n super(props)\n\n this.styles = {\n panel: {\n backgroundColor: props.theme.light,\n boxShadow: '-1px 0 3px 0 rgba(0, 0, 0, 0.25)',\n position: 'absolute',\n right: 0,\n top: 0,\n height: '100%',\n width: '30em',\n margin: 0,\n display: 'grid',\n gridTemplateRows: 'auto 1fr 12%',\n },\n body: {\n padding: '1em',\n flex: 1,\n },\n header: {\n borderBottom: `1px solid ${props.theme.neutralLight}`,\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'center',\n padding: '1em 0.2em',\n },\n headerContent: {\n display: 'flex',\n alignItems: 'center',\n padding: '0 1em',\n },\n footerButton: {\n display: 'block',\n height: '100%',\n width: '100%',\n borderTop: `1px solid ${props.theme.neutralLight}`,\n },\n title: {\n fontSize: '1.1em',\n paddingLeft: '1em',\n },\n }\n }\n\n render() {\n return (\n \n
\n
\n
\n
{this.props.title}
\n
\n
\n \n \n \n
\n
\n
{this.props.children}
\n
\n \n
\n
\n )\n }\n}\n\nFilterPanel.propTypes = {\n applyFilters: PropTypes.func,\n children: PropTypes.node,\n hasChanges: PropTypes.bool,\n theme: themeShape,\n title: PropTypes.node,\n toggle: PropTypes.func,\n}\n\nexport default themeable(FilterPanel)\nexport { default as Filter } from './Filter'\nexport { default as TypeaheadFilter } from './TypeaheadFilter'\n","export const SET_SELECTED_PIVOT =\n 'app/SolutionCenterFilterPanel/SET_SELECTED_PIVOT'\nexport const CLEAR_SELECTED_PIVOTS =\n 'app/SolutionCenterFilterPanel/CLEAR_SELECTED_PIVOTS'\nexport const ADD_TAG_FILTER = 'app/SolutionCenterFilterPanel/ADD_TAG_FILTER'\n","import { fromJS, Map } from 'immutable'\nimport { handleActions } from 'redux-actions'\nimport {\n SET_SELECTED_PIVOT,\n CLEAR_SELECTED_PIVOTS,\n ADD_TAG_FILTER,\n} from './constants'\n\nconst initialState = fromJS({\n selectedPivots: {},\n tagFilters: [],\n})\n\nconst solutionCenterReducer = handleActions(\n {\n [ADD_TAG_FILTER]: (state, { payload }) =>\n state.updateIn(['tagFilters'], tagFilters => tagFilters.push(payload)),\n [CLEAR_SELECTED_PIVOTS]: state => state.set('selectedPivots', Map()),\n [SET_SELECTED_PIVOT]: (state, { payload }) => {\n return state.setIn(\n ['selectedPivots', payload.type],\n fromJS(payload.value)\n )\n },\n },\n initialState\n)\n\nexport default solutionCenterReducer\n","import { defineMessages } from 'react-intl'\n\nexport default defineMessages({\n filtersTitle: {\n id: 'app.containers.SolutionCenterFilterPanel.filtersTitle',\n defaultMessage: 'Filters',\n },\n dateRange: {\n id: 'app.containers.SolutionCenterFilterPanel.dateRange',\n defaultMessage: 'Date Range',\n },\n days1: {\n id: 'app.containers.SolutionCenterFilterPanel.days1',\n defaultMessage: 'Since 24 Hours',\n },\n days7: {\n id: 'app.containers.SolutionCenterFilterPanel.days7',\n defaultMessage: 'Since 7 Days Ago',\n },\n days30: {\n id: 'app.containers.SolutionCenterFilterPanel.days30',\n defaultMessage: 'Since 30 Days Ago',\n },\n days90: {\n id: 'app.containers.SolutionCenterFilterPanel.days90',\n defaultMessage: 'Since 90 Days Ago',\n },\n accountFilterTitle: {\n id: 'app.containers.SolutionCenterFilterPanel.accountFilterTitle',\n defaultMessage: 'Account',\n },\n tagFilterTitle: {\n id: 'app.containers.SolutionCenterFilterPanel.tagFilterTitle',\n defaultMessage: 'Tag Set',\n },\n})\n","import {\n SET_SELECTED_PIVOT,\n CLEAR_SELECTED_PIVOTS,\n ADD_TAG_FILTER,\n} from './constants'\nimport { createAction } from 'redux-actions'\n\nexport const setSelectedPivot = createAction(SET_SELECTED_PIVOT)\nexport const clearSelectedPivots = createAction(CLEAR_SELECTED_PIVOTS)\nexport const addTagFilter = createAction(ADD_TAG_FILTER)\n","import { createSelector } from 'reselect'\nimport { Map, List } from 'immutable'\n\nexport const selectSolutionCenterFilterPanelDomain = state =>\n state.get('solutionCenterFilterPanel') || Map()\n\nexport const selectSelectedPivots = createSelector(\n selectSolutionCenterFilterPanelDomain,\n state => state.get('selectedPivots') || Map()\n)\n\nexport const selectTagFilters = createSelector(\n selectSolutionCenterFilterPanelDomain,\n state => state.get('tagFilters') || List()\n)\n","import React from 'react'\nimport PropTypes from 'prop-types'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport { connect } from 'react-redux'\nimport { createStructuredSelector } from 'reselect'\nimport { compose, bindActionCreators } from 'redux'\nimport { isImmutable } from 'immutable'\nimport { FormattedMessage, injectIntl } from 'react-intl'\n\nimport SelectBar from 'components/SelectBar'\nimport DateRangePicker from 'components/DateRangePicker'\nimport { CLOUD_TYPES } from 'appConstants'\nimport IHelp from 'containers/IHelp'\nimport AccountSelector from 'components/AccountSelector'\nimport injectReducer from 'utils/injectReducer'\nimport { selectPivot } from 'containers/SonraiData/selectors'\nimport { exists } from 'utils/sonraiUtils'\nimport {\n setPivot,\n fetchTags,\n fetchDataContainers,\n fetchAccounts,\n fetchSubscriptions,\n} from 'containers/SonraiData/actions'\nimport FilterPanel, { Filter, TypeaheadFilter } from 'components/FilterPanel'\nimport { PIVOT_FILEDS } from 'query-builder'\nimport {\n selectAccounts,\n selectDataContainers,\n selectTags,\n selectSubscriptions,\n} from 'containers/SonraiData/selectors'\nimport _ from 'lodash'\nimport reducer from './reducer'\nimport messages from './messages'\nimport { setSelectedPivot, clearSelectedPivots, addTagFilter } from './actions'\nimport { selectSelectedPivots, selectTagFilters } from './selectors'\n\nexport class SolutionCenterFilterPanel extends React.Component {\n onChangeAccountFilter = selectedAccounts => {\n this.props.setSelectedPivot({\n type: PIVOT_FILEDS.ACCOUNTS,\n value:\n Array.isArray(selectedAccounts) && selectedAccounts.length > 0\n ? selectedAccounts\n : null,\n })\n }\n\n onChangeSpecificDates = (newDates = {}) => {\n this.props.setSelectedPivot({\n type: PIVOT_FILEDS.SPECIFIC_DATES,\n value: newDates.startDate && newDates.endDate ? newDates : null,\n })\n }\n\n onChangePivotFilter = (type, selectedOption) => {\n if (Array.isArray(selectedOption)) {\n this.props.setSelectedPivot({\n type,\n value:\n selectedOption.length === 0\n ? null\n : selectedOption.map(option => option.value),\n })\n } else {\n this.props.setSelectedPivot({\n type,\n value: _.isEmpty(selectedOption) ? null : selectedOption.value,\n })\n }\n }\n\n onChangeSpecificDate = newDates => {\n this.props.setSelectedPivot({\n type: PIVOT_FILEDS.SPECIFIC_DATES,\n value: newDates,\n })\n }\n\n hasChanges = () => {\n const nonEmptyPivots = this.props.selectedPivots.filter(pivot => {\n if (isImmutable(pivot)) {\n return !pivot.isEmpty()\n } else {\n return exists(pivot)\n }\n })\n\n return !nonEmptyPivots.isEmpty()\n }\n\n applyFilters = () => {\n this.props.selectedPivots.forEach((value, key) => {\n this.props.setPivot({\n type: key,\n value,\n })\n })\n\n this.props.clearSelectedPivots()\n }\n\n getSelectedValue = type => {\n const pivotValue = this.props.selectedPivots.has(type)\n ? this.props.selectedPivots.get(type)\n : this.props.pivot.get(type)\n\n return isImmutable(pivotValue) ? pivotValue.toJS() : pivotValue\n }\n\n getTagOptions = () => {\n const serverTags = this.props.tags\n .filter(tag => !!tag.get('key'))\n .sortBy(tag => tag.get('key').toLowerCase())\n .toJS()\n .map(tag => ({ value: tag.key, label: tag.key }))\n\n const additionalOptions = this.props.tagFilters\n .toJS()\n .map(tagStr => ({ value: tagStr, label: tagStr }))\n\n return serverTags.concat(additionalOptions)\n }\n\n render() {\n if (!this.props.isOpen) {\n return null\n }\n\n return (\n \n \n \n \n\n \n \n \n\n \n ({\n label: value,\n value,\n }))\n : []\n }\n onChange={this.onChangeAccountFilter}\n isMulti\n />\n \n\n \n \n \n \n \n }\n >\n \n \n\n \n \n \n \n )\n }\n}\n\nSolutionCenterFilterPanel.propTypes = {\n addTagFilter: PropTypes.func.isRequired,\n accounts: ImmutablePropTypes.list.isRequired,\n clearSelectedPivots: PropTypes.func.isRequired,\n dataContainers: ImmutablePropTypes.list.isRequired,\n fetchDataContainers: PropTypes.func.isRequired,\n fetchTags: PropTypes.func.isRequired,\n fetchAccounts: PropTypes.func.isRequired,\n subscriptions: ImmutablePropTypes.list.isRequired,\n fetchSubscriptions: PropTypes.func.isRequired,\n intl: PropTypes.shape({\n formatMessage: PropTypes.func.isRequired,\n }).isRequired,\n isOpen: PropTypes.bool,\n pivot: ImmutablePropTypes.map.isRequired,\n selectedPivots: ImmutablePropTypes.map.isRequired,\n setPivot: PropTypes.func.isRequired,\n setSelectedPivot: PropTypes.func,\n tags: ImmutablePropTypes.listOf(\n ImmutablePropTypes.contains({\n key: PropTypes.string,\n })\n ).isRequired,\n tagFilters: ImmutablePropTypes.listOf(PropTypes.string).isRequired,\n toggle: PropTypes.func,\n}\n\nconst mapStateToProps = createStructuredSelector({\n subscriptions: selectSubscriptions,\n accounts: selectAccounts,\n dataContainers: selectDataContainers,\n pivot: selectPivot,\n selectedPivots: selectSelectedPivots,\n tags: selectTags,\n tagFilters: selectTagFilters,\n})\n\nfunction mapDispatchToProps(dispatch) {\n return bindActionCreators(\n {\n addTagFilter,\n clearSelectedPivots,\n setPivot,\n fetchDataContainers,\n fetchTags,\n fetchAccounts,\n fetchSubscriptions,\n setSelectedPivot,\n },\n dispatch\n )\n}\n\nconst withConnect = connect(mapStateToProps, mapDispatchToProps)\n\nconst withReducer = injectReducer({ key: 'solutionCenterFilterPanel', reducer })\n\nexport default compose(\n withConnect,\n injectIntl,\n withReducer\n)(SolutionCenterFilterPanel)\n","import { defineMessages } from 'react-intl'\n\nexport default defineMessages({\n cancelScEditButton: {\n id: 'app.containers.RenameSCModal.cancelScEditButton',\n defaultMessage: 'Cancel',\n },\n saveScEditButton: {\n id: 'app.containers.RenameSCModal.saveScEditButton',\n defaultMessage: 'Save',\n },\n hideCard: {\n id: 'app.containers.CardActionsMenu.hideCard',\n defaultMessage: 'Hide Solution Card',\n },\n renameCard: {\n id: 'app.containers.CardActionsMenu.renameCard',\n defaultMessage: 'Rename Solution Card',\n },\n deleteCard: {\n id: 'app.containers.CardActionsMenu.deleteCard',\n defaultMessage: 'Delete Solution Card',\n },\n copyCard: {\n id: 'app.containers.CardActionsMenu.copyCard',\n defaultMessage: 'Copy Solution Card',\n },\n saveLayoutButton: {\n id: 'app.containers.SolutionCenterActions.saveLayoutButton',\n defaultMessage: 'Save layout changes',\n },\n manageHiddenCards: {\n id: 'app.containers.SolutionCenterActions.manageHiddenCards',\n defaultMessage: 'Manage Hidden Solution Cards',\n },\n})\n","import React from 'react'\n\nimport PropTypes from 'prop-types'\nimport { connect } from 'react-redux'\nimport { createStructuredSelector } from 'reselect'\nimport { compose, bindActionCreators } from 'redux'\nimport { FormattedMessage } from 'react-intl'\nimport { Modal, ModalHeader, ModalBody, ModalFooter, Input } from 'reactstrap'\n\nimport TextLink from 'components/TextLink'\nimport Icon from 'components/Icon'\nimport FormLabel from 'components/FormLabel'\nimport Button from 'components/Button'\nimport { updateSolutionCard } from 'containers/SolutionCenter/actions'\n\nimport {\n selectSavingScEdits,\n selectSelectedCardName,\n} from 'containers/SolutionCenterCards/selectors'\nimport messages from './messages.js'\n\nexport class ModalRenameSC extends React.Component {\n constructor(props) {\n super(props)\n this.state = {\n name: props.originalName,\n }\n }\n\n componentDidUpdate(oldProps) {\n if (oldProps.savingScEdits && !this.props.savingScEdits) {\n this.props.toggle()\n }\n\n if (oldProps.originalName !== this.props.originalName) {\n this.setState({\n name: this.props.originalName,\n })\n }\n }\n\n setName = e => {\n this.setState({\n name: e.target.value,\n })\n }\n\n updateSolutionCard = () => {\n this.props.updateSolutionCard({\n srn: this.props.editCardId,\n name: this.state.name,\n })\n }\n\n render() {\n return (\n \n \n Rename Solution Card\n \n \n \n New Name\n \n \n \n \n \n \n \n {' '}\n \n \n )\n }\n}\n\nModalRenameSC.propTypes = {\n isOpen: PropTypes.bool,\n originalName: PropTypes.string,\n savingScEdits: PropTypes.bool,\n toggle: PropTypes.func,\n updateSolutionCard: PropTypes.func,\n}\n\nconst mapStateToProps = createStructuredSelector({\n originalName: selectSelectedCardName,\n savingScEdits: selectSavingScEdits,\n})\n\nfunction mapDispatchToProps(dispatch) {\n return bindActionCreators(\n {\n updateSolutionCard,\n },\n dispatch\n )\n}\n\nconst withConnect = connect(\n mapStateToProps,\n mapDispatchToProps\n)\n\nexport default compose(withConnect)(ModalRenameSC)\n","import React from 'react'\n\nimport PropTypes from 'prop-types'\nimport { connect } from 'react-redux'\nimport { compose, bindActionCreators } from 'redux'\nimport { Modal, ModalHeader, ModalFooter, ModalBody } from 'reactstrap'\n\nimport { TextLink } from 'components/TextLink'\nimport Button from 'components/Button'\nimport { deleteSolutionCard } from 'containers/SolutionCenter/actions'\n\nexport class ModalConfirmDelete extends React.Component {\n handleDeleteSc = () => {\n this.props.deleteSolutionCard(this.props.cardId)\n this.props.toggle()\n }\n\n render() {\n return (\n \n \n Are You Sure You Want To Delete This Solution Card?\n \n\n Deleting a Solution Card cannot be undone\n \n \n Cancel\n \n \n \n \n )\n }\n}\n\nModalConfirmDelete.propTypes = {\n deleteSolutionCard: PropTypes.func.isRequired,\n cardId: PropTypes.string,\n isOpen: PropTypes.bool,\n toggle: PropTypes.func,\n}\n\nfunction mapDispatchToProps(dispatch) {\n return bindActionCreators(\n {\n deleteSolutionCard,\n },\n dispatch\n )\n}\n\nconst withConnect = connect(\n undefined,\n mapDispatchToProps\n)\n\nexport default compose(withConnect)(ModalConfirmDelete)\n","import React from 'react'\nimport PropTypes from 'prop-types'\nimport { connect } from 'react-redux'\nimport { createStructuredSelector } from 'reselect'\nimport { compose, bindActionCreators } from 'redux'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport { List } from 'immutable'\nimport { FormattedMessage } from 'react-intl'\nimport { Modal, ModalHeader, ModalBody, ModalFooter, Table } from 'reactstrap'\n\nimport { selectUserProfile } from 'containers/UserProfileData/selectors'\nimport Button from 'components/Button'\nimport TextLink from 'components/TextLink'\nimport { selectCards } from 'containers/SolutionCenter/selectors'\nimport { unhideSolutionCards } from 'containers/UserProfileData/actions'\n\nimport messages from './messages.js'\n\nexport class ModalHiddenSCManager extends React.Component {\n constructor(props) {\n super(props)\n\n this.state = {\n reVisibleCards: [],\n }\n }\n\n toggleCard = srn => {\n let { reVisibleCards } = this.state\n if (reVisibleCards.includes(srn)) {\n let arr = [...reVisibleCards]\n var index = arr.indexOf(srn)\n arr.splice(index, 1)\n this.setState({ reVisibleCards: arr })\n } else {\n this.setState(oldState => ({\n reVisibleCards: [...oldState.reVisibleCards, srn],\n }))\n }\n }\n\n handleUnHideSCCards = () => {\n const srns = this.state.reVisibleCards\n\n if (srns.length > 0) {\n this.props.unhideSolutionCards(srns)\n }\n\n this.props.toggle()\n this.setState({ reVisibleCards: [] })\n }\n\n render() {\n const hiddenCardIds = this.props.userProfile.get(\n 'hiddenSolutionCards',\n List()\n )\n\n const hiddenCards = this.props.solutionCards\n .filter(solutionCard => hiddenCardIds.includes(solutionCard.get('srn')))\n .toJS()\n\n return (\n \n \n Edit Hidden Solution Cards\n \n \n \n \n Card Name | \n Widgets | \n Status | \n | \n \n \n {hiddenCards.map(card => (\n \n {card.name} | \n {card.widgets && card.widgets.length} Widgets | \n \n {this.state.reVisibleCards.includes(card.srn)\n ? 'Visible'\n : 'Hidden'}\n | \n \n this.toggleCard(card.srn)}\n >\n {this.state.reVisibleCards.includes(card.srn)\n ? 'Hide'\n : 'Show'}\n \n | \n
\n ))}\n \n
\n \n \n \n \n \n\n \n \n \n )\n }\n}\n\nModalHiddenSCManager.propTypes = {\n isOpen: PropTypes.bool,\n solutionCards: ImmutablePropTypes.list.isRequired,\n toggle: PropTypes.func,\n unhideSolutionCards: PropTypes.func.isRequired,\n userProfile: ImmutablePropTypes.map.isRequired,\n}\n\nconst mapStateToProps = createStructuredSelector({\n userProfile: selectUserProfile,\n solutionCards: selectCards,\n})\n\nfunction mapDispatchToProps(dispatch) {\n return bindActionCreators(\n {\n unhideSolutionCards,\n },\n dispatch\n )\n}\n\nconst withConnect = connect(\n mapStateToProps,\n mapDispatchToProps\n)\n\nexport default compose(withConnect)(ModalHiddenSCManager)\n","import React from 'react'\n\nimport PropTypes from 'prop-types'\nimport { connect } from 'react-redux'\nimport { createStructuredSelector } from 'reselect'\nimport { compose, bindActionCreators } from 'redux'\nimport { FormattedMessage } from 'react-intl'\nimport { Modal, ModalHeader, ModalBody, ModalFooter, Input } from 'reactstrap'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport TextLink from 'components/TextLink'\nimport Icon from 'components/Icon'\nimport FormLabel from 'components/FormLabel'\nimport Button from 'components/Button'\nimport { copySolutionCard } from 'containers/SolutionCenter/actions'\nimport { selectIsScCopySaving } from 'containers/SolutionCenter/selectors'\nimport {\n selectSelectedCardName,\n selectSelectedCard,\n selectSortedCards,\n} from 'containers/SolutionCenterCards/selectors'\nimport messages from './messages.js'\n\nexport class ModalCopySC extends React.Component {\n constructor(props) {\n super(props)\n this.state = {\n name: props.originalName,\n }\n }\n\n componentDidUpdate(oldProps) {\n if (oldProps.saving && !this.props.saving) {\n this.props.toggle()\n }\n\n if (oldProps.originalName !== this.props.originalName) {\n this.setState({\n name: this.props.originalName,\n })\n }\n }\n\n copySolutionCard = () => {\n const title =\n this.state.name !== '' ? this.state.name.toLowerCase() : this.state.name\n const sortedCardNames = this.props.sortedCards.map(card =>\n card.get('name').toLowerCase()\n )\n if (title !== '' && !sortedCardNames.includes(title)) {\n this.props.copySolutionCard({\n title: this.state.name,\n widgets: this.props.selectedCard.get('widgets'),\n })\n } else {\n this.setState({ showMessage: true })\n }\n }\n\n setName = e => {\n this.setState({\n name: e.target.value,\n })\n }\n\n render() {\n return (\n \n Copy Solution Card\n \n \n New Solution Card Name\n \n \n \n \n \n \n \n {' '}\n \n \n )\n }\n}\n\nModalCopySC.propTypes = {\n isOpen: PropTypes.bool,\n originalName: PropTypes.string,\n saving: PropTypes.bool,\n toggle: PropTypes.func,\n sortedCards: ImmutablePropTypes.iterable,\n selectedCard: ImmutablePropTypes.map.isRequired,\n copySolutionCard: PropTypes.func,\n}\n\nconst mapStateToProps = createStructuredSelector({\n originalName: selectSelectedCardName,\n selectedCard: selectSelectedCard,\n sortedCards: selectSortedCards,\n saving: selectIsScCopySaving,\n})\n\nfunction mapDispatchToProps(dispatch) {\n return bindActionCreators(\n {\n copySolutionCard,\n },\n dispatch\n )\n}\n\nconst withConnect = connect(\n mapStateToProps,\n mapDispatchToProps\n)\n\nexport default compose(withConnect)(ModalCopySC)\n","import React from 'react'\nimport PropTypes from 'prop-types'\nimport { connect } from 'react-redux'\nimport { createStructuredSelector } from 'reselect'\nimport { compose, bindActionCreators } from 'redux'\nimport { Dropdown, DropdownToggle } from 'reactstrap'\nimport { FormattedMessage } from 'react-intl'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport { List } from 'immutable'\nimport CreatedByBadge from 'components/CreatedByBadge'\nimport permissionChecker from 'containers/PermissionChecker'\nimport { hideSolutionCard } from 'containers/UserProfileData/actions'\nimport Icon from 'components/Icon'\nimport BorderlessButton from 'components/BorderlessButton'\nimport { selectUserProfile } from 'containers/UserProfileData/selectors'\nimport DropdownMenu from 'components/StyledReactstrapDropdownMenu'\n\nimport messages from './messages'\nimport { selectSelectedCard } from 'containers/SolutionCenterCards/selectors'\n\nconst styles = {\n moreButton: {\n border: 'none',\n display: 'inline',\n padding: '0.3em',\n },\n link: {\n display: 'block',\n width: '100%',\n marginBottom: '0.5em',\n paddingTop: '0.25em',\n paddingBottom: '0.25em',\n paddingLeft: '1em',\n paddingRight: '1em',\n cursor: 'pointer',\n ':hover': { backgroundColor: 'rgba(116, 180, 223, 0.4)' },\n textAlign: 'left',\n border: 'none',\n boxShadow: 'none',\n },\n}\n\nexport class CardActionsMenu extends React.Component {\n constructor(props) {\n super(props)\n this.state = {\n dropdownExpanded: false,\n }\n }\n\n toggleActionMenu = () => {\n this.setState(oldState => {\n return {\n dropdownExpanded: !oldState.dropdownExpanded,\n }\n })\n }\n\n doCardAction = action => {\n action(this.props.card.get('srn'))\n this.toggleActionMenu()\n }\n\n getActions = () => {\n const canEdit = this.props.userHasPermission({\n permissionName: 'edit.solutioncards',\n resourceId: this.props.card.get('resourceId'),\n })\n\n const canCreate = this.props.userHasPermission({\n permissionName: 'edit.solutioncards',\n resourceId: ({ org }) => `/org/${org}/`,\n })\n\n const actions = [\n this.doCardAction(this.props.hideSolutionCard)}\n style={styles.link}\n >\n \n ,\n ]\n\n if (canEdit) {\n actions.push(\n this.doCardAction(this.props.renameSolutionCard)}\n style={styles.link}\n >\n \n \n )\n }\n\n if (canEdit) {\n actions.push(\n this.doCardAction(this.props.deleteSolutionCard)}\n style={styles.link}\n >\n \n \n )\n }\n\n if (canCreate) {\n actions.push(\n this.doCardAction(this.props.copySolutionCard)}\n style={styles.link}\n >\n \n \n )\n }\n\n const hasHiddenCards = !this.props.userProfile\n .get('hiddenSolutionCards', List())\n .isEmpty()\n\n actions.push(\n this.doCardAction(this.props.toggleHiddenSCManagerModal)}\n style={styles.link}\n disabled={!hasHiddenCards}\n >\n \n \n )\n\n actions.push(\n \n Created By: \n \n \n )\n\n return actions\n }\n\n render() {\n return (\n \n \n \n \n \n {this.getActions()}\n \n \n )\n }\n}\n\nCardActionsMenu.propTypes = {\n card: ImmutablePropTypes.map.isRequired,\n deleteSolutionCard: PropTypes.func.isRequired,\n copySolutionCard: PropTypes.func.isRequired,\n hideSolutionCard: PropTypes.func.isRequired,\n userHasPermission: PropTypes.func.isRequired,\n renameSolutionCard: PropTypes.func.isRequired,\n toggleHiddenSCManagerModal: PropTypes.func,\n userProfile: ImmutablePropTypes.map.isRequired,\n}\n\nconst mapStateToProps = createStructuredSelector({\n card: selectSelectedCard,\n userProfile: selectUserProfile,\n})\nfunction mapDispatchToProps(dispatch) {\n return bindActionCreators(\n {\n hideSolutionCard,\n },\n dispatch\n )\n}\nconst withConnect = connect(mapStateToProps, mapDispatchToProps)\n\nexport default compose(withConnect, permissionChecker)(CardActionsMenu)\n","import React from 'react'\nimport PropTypes from 'prop-types'\nimport { connect } from 'react-redux'\nimport { createStructuredSelector } from 'reselect'\nimport { compose, bindActionCreators } from 'redux'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport { injectIntl } from 'react-intl'\n\nimport BorderlessButton from 'components/BorderlessButton'\nimport Icon from 'components/Icon'\nimport { saveLayout } from 'containers/SolutionCenter/actions'\nimport {\n selectSelectedCard,\n selectSavingLayout,\n selectHasUnsavedChanges,\n} from 'containers/SolutionCenterCards/selectors'\nimport permissionChecker from 'containers/PermissionChecker'\nimport { selectPivot } from 'containers/SonraiData/selectors'\nimport IconLayer from 'components/IconLayer'\nimport IconCount from 'components/IconCount'\nimport themeable, { themeShape } from 'containers/ThemeManager/Themeable'\n\nimport ModalRenameSC from './ModalRenameSC'\nimport ModalConfirmDelete from './ModalConfirmDelete'\nimport ModalHiddenSCManager from './ModalHiddenSCManager'\nimport ModalCopySC from './ModalCopySC'\nimport messages from './messages'\nimport CardActionsMenu from './CardActionsMenu'\n\nexport class SolutionCenterCardActions extends React.Component {\n styles = {\n toolbarButton: {\n padding: '0 0.5em',\n },\n saveButton: {\n boxShadow: ' 0 0 0 rgba(204,169,44, 0.4)',\n animation: 'pulse 2s infinite',\n },\n cardActions: {\n display: 'flex',\n },\n }\n\n state = {\n editingScNameId: null,\n deletingScId: null,\n showHiddenScManagerModal: false,\n copyingScModalOpen: false,\n }\n\n toggleShowDeleteConfirmationModal = () => {\n this.setState({\n deletingScId: null,\n })\n }\n\n showConfirmDelete = cardId => {\n this.setState({\n deletingScId: cardId,\n })\n }\n\n toggleHiddenSCManagerModal = () => {\n this.setState(currentState => ({\n showHiddenScManagerModal: !currentState.showHiddenScManagerModal,\n }))\n }\n\n toggleRenameScModal = () => {\n this.setState({\n editingScNameId: null,\n })\n }\n\n renameSolutionCard = cardId => {\n this.setState({\n editingScNameId: cardId,\n })\n }\n\n copySolutionCard = () => {\n this.setState({\n copyingScModalOpen: true,\n })\n }\n\n saveLayout = () => {\n this.props.saveLayout(this.props.selectedSolutionCard)\n }\n\n renderSaveButton = () => {\n if (this.props.savingLayout) {\n return (\n \n \n \n )\n }\n\n const canUpdateCurrentCard = this.props.userHasPermission({\n permissionName: 'edit.solutioncards',\n resourceId: this.props.selectedSolutionCard.get('resourceId'),\n })\n\n if (!this.props.hasUnsavedChanges || !canUpdateCurrentCard) {\n return null\n }\n\n return (\n \n \n \n )\n }\n\n toggleCopyingScModal = () => {\n this.setState(old => {\n return {\n copyingScModalOpen: !old.copyingScModalOpen,\n }\n })\n }\n\n render() {\n return (\n \n \n \n \n \n {this.renderSaveButton()}\n \n {!this.props.pivot.isEmpty() ? (\n \n \n \n \n ) : (\n \n )}\n \n \n
\n )\n }\n}\n\nSolutionCenterCardActions.propTypes = {\n hasUnsavedChanges: PropTypes.bool,\n pivot: ImmutablePropTypes.map.isRequired,\n savingLayout: PropTypes.bool,\n saveLayout: PropTypes.func.isRequired,\n selectedSolutionCard: ImmutablePropTypes.map.isRequired,\n theme: themeShape,\n toggleFiltersPanel: PropTypes.func,\n userHasPermission: PropTypes.func,\n}\n\nconst mapStateToProps = createStructuredSelector({\n hasUnsavedChanges: selectHasUnsavedChanges,\n pivot: selectPivot,\n savingLayout: selectSavingLayout,\n selectedSolutionCard: selectSelectedCard,\n})\n\nfunction mapDispatchToProps(dispatch) {\n return bindActionCreators(\n {\n saveLayout,\n },\n dispatch\n )\n}\n\nconst withConnect = connect(mapStateToProps, mapDispatchToProps)\n\nexport default compose(\n withConnect,\n injectIntl,\n permissionChecker,\n themeable\n)(SolutionCenterCardActions)\n","/*\n *\n * SolutionCenter reducer\n *\n */\n\nimport { fromJS, List } from 'immutable'\nimport { handleActions } from 'redux-actions'\nimport {\n LOAD_SOLUTION_CARDS,\n TOGGLE_SC_WIDGET_STATIC,\n UPDATE_WIDGET,\n UPDATE_WIDGET_SUCCESS,\n ADD_SOLUTION_CARD_SUCCESS,\n DELETE_SOLUTION_CARD_SUCCESS,\n ADD_WIDGET_SUCCESS,\n REMOVE_WIDGET_SUCCESS,\n SET_WIDGET_OPTIONS,\n UPDATE_SOLUTION_CARD_SUCCESS,\n SET_SHOW_CREATE_WIDGET_MODAL,\n COPY_SOLUTION_CARD,\n COPY_SOLUTION_CARD_SUCCESS,\n} from './constants'\n\nconst initialState = fromJS({\n solutionCards: [],\n showCreateWidgetModal: false,\n})\n\nconst solutionCenterReducer = handleActions(\n {\n [ADD_SOLUTION_CARD_SUCCESS]: (state, { payload }) =>\n state\n .update('solutionCards', cards => cards.push(fromJS(payload)))\n .set('showCreateWidgetModal', true),\n [ADD_WIDGET_SUCCESS]: (state, { payload }) => {\n const solutionCardIndex = state\n .get('solutionCards')\n .findIndex(card => card.get('srn') === payload.cardsrn)\n\n return state\n .updateIn(['solutionCards', solutionCardIndex, 'widgets'], widgets =>\n widgets.push(fromJS(payload.widget))\n )\n .set('showCreateWidgetModal', false)\n },\n [DELETE_SOLUTION_CARD_SUCCESS]: (state, { payload }) => {\n const solutionCardIndex = state\n .get('solutionCards')\n .findIndex(card => card.get('srn') === payload)\n\n return state.deleteIn(['solutionCards', solutionCardIndex])\n },\n [LOAD_SOLUTION_CARDS]: (state, { payload }) => {\n return state.set('solutionCards', fromJS(payload.cards))\n },\n [REMOVE_WIDGET_SUCCESS]: (state, { payload }) => {\n const solutionCardIndex = state\n .get('solutionCards')\n .findIndex(card => card.get('srn') === payload.cardId)\n const widgetIndex = state\n .getIn(['solutionCards', solutionCardIndex, 'widgets'], List())\n .findIndex(widget => widget.get('srn') === payload.widgetId)\n\n return state.deleteIn([\n 'solutionCards',\n solutionCardIndex,\n 'widgets',\n widgetIndex,\n ])\n },\n [SET_SHOW_CREATE_WIDGET_MODAL]: (state, { payload }) =>\n state.set('showCreateWidgetModal', payload),\n [TOGGLE_SC_WIDGET_STATIC]: (state, { payload }) => {\n const solutionCardIndex = state\n .get('solutionCards')\n .findIndex(card => card.get('srn') === payload.cardId)\n const widgetIndex = state\n .getIn(['solutionCards', solutionCardIndex, 'widgets'], List())\n .findIndex(widget => widget.get('srn') === payload.widgetId)\n\n return state.updateIn(\n ['solutionCards', solutionCardIndex, 'widgets', widgetIndex],\n widget => widget.set('static', !widget.get('static'))\n )\n },\n [UPDATE_SOLUTION_CARD_SUCCESS]: (state, { payload }) => {\n const solutionCardIndex = state\n .get('solutionCards')\n .findIndex(card => card.get('srn') === payload.srn)\n\n return state.mergeIn(['solutionCards', solutionCardIndex], payload)\n },\n [UPDATE_WIDGET]: (state, { payload }) => {\n const solutionCardIndex = state\n .get('solutionCards')\n .findIndex(card => card.get('srn') === payload.cardId)\n const widgetIndex = state\n .getIn(['solutionCards', solutionCardIndex, 'widgets'], List())\n .findIndex(widget => widget.get('srn') === payload.widget.srn)\n\n return state.setIn(\n ['solutionCards', solutionCardIndex, 'widgets', widgetIndex],\n fromJS(payload.widget)\n )\n },\n [UPDATE_WIDGET_SUCCESS]: (state, { payload }) => {\n const solutionCardIndex = state\n .get('solutionCards')\n .findIndex(card => card.get('srn') === payload.cardsrn)\n const widgetIndex = state\n .getIn(['solutionCards', solutionCardIndex, 'widgets'], List())\n .findIndex(widget => widget.get('srn') === payload.widget.srn)\n\n return state.mergeIn(\n ['solutionCards', solutionCardIndex, 'widgets', widgetIndex],\n fromJS(payload.widget)\n )\n },\n [SET_WIDGET_OPTIONS]: (state, { payload }) => {\n const solutionCardIndex = state\n .get('solutionCards')\n .findIndex(card => card.get('srn') === payload.cardId)\n const widgetIndex = state\n .getIn(['solutionCards', solutionCardIndex, 'widgets'], List())\n .findIndex(widget => widget.get('srn') === payload.widgetId)\n\n return state.mergeIn(\n ['solutionCards', solutionCardIndex, 'widgets', widgetIndex, 'options'],\n fromJS(payload.options)\n )\n },\n [COPY_SOLUTION_CARD]: state => state.set('copySolutionCardSaving', true),\n [COPY_SOLUTION_CARD_SUCCESS]: (state, { payload }) => {\n const solutionCardIndex = state\n .get('solutionCards')\n .findIndex(card => card.get('srn') === payload.card.srn)\n\n return state\n .update('solutionCards', cards => cards.push(fromJS(payload.card)))\n .setIn(\n ['solutionCards', solutionCardIndex, 'widgets'],\n fromJS(payload.widgets)\n )\n .set('copySolutionCardSaving', false)\n },\n },\n initialState\n)\n\nexport default solutionCenterReducer\n","export const FETCH_SOLUTION_CARDS_QUERY = `\n query getSCCards {\n SolutionCards {\n count\n items {\n __typename\n name\n createdDate\n sid\n srn\n lastModified\n resourceId\n createdBy\n contains {\n items {\n resourceId\n title\n subtitle\n name\n resultLayout\n static\n type\n widgetLocation\n createdDate\n createdBy\n sid\n srn\n lastModified\n widgetSize\n selection\n options\n contains {\n items {\n name\n sid\n srn\n ... on Search {\n rootQueryName\n lastModified\n createdDate\n query\n resourceId\n }\n }\n }\n }\n }\n }\n }\n }\n`\n","import { all, put, takeLatest, take, race, call } from 'redux-saga/effects'\nimport { Map, List } from 'immutable'\nimport { fetchSavedSearches } from 'containers/SonraiData/sagas'\nimport { push } from 'connected-react-router'\nimport qs from 'query-string'\nimport omit from 'lodash/omit'\nimport { delay } from 'redux-saga'\nimport {\n loadSolutionCards,\n handleSCAdd,\n saveLayoutSuccess,\n addSolutionCardSuccess,\n deleteSolutionCardSuccess,\n addWidgetSuccess,\n removeWidgetSuccess,\n updateWidgetSuccess,\n updateSolutionCardSuccess,\n copySolutionCardSuccess,\n} from './actions'\nimport {\n GET_SOLUTION_CARDS,\n UPDATE_EDIT_WIDGET,\n HANDLE_SC_ADD,\n DELETE_SOLUTION_CARD,\n ADD_SC_WIDGET,\n REMOVE_SC_WIDGET,\n SAVE_LAYOUT,\n UPDATE_SOLUTION_CARD,\n START_UPDATE_SC_POLL,\n STOP_UPDATE_SC_POLL,\n COPY_SOLUTION_CARD,\n} from './constants'\nimport { FETCH_SOLUTION_CARDS_QUERY } from './queries'\nimport { getClient } from 'apolloClient'\nimport gql from 'graphql-tag'\n\nfunction* getSolnCards() {\n try {\n const client = getClient()\n const result = yield client.query({\n query: gql`\n ${FETCH_SOLUTION_CARDS_QUERY}\n `,\n })\n\n if (result.data.SolutionCards.items === null) {\n throw new Error('Bad formatting of response from server: items is null')\n }\n\n let cards = result.data.SolutionCards.items.map(sysCard => {\n return {\n __typename: sysCard.__typename,\n sid: sysCard.sid,\n srn: sysCard.srn,\n name: sysCard.name,\n resourceId: sysCard.resourceId,\n createdDate: sysCard.createdDate,\n createdBy: sysCard.createdBy,\n widgets: sysCard.contains.items.map(widget => {\n if (widget.contains.items.length > 0) {\n return {\n ...widget,\n query: widget.contains.items[0], // TODO: Handle multiple widget queries\n }\n } else {\n return widget\n }\n }),\n }\n })\n\n if (cards.length === 0) {\n yield put(handleSCAdd('Dashboard 1'))\n } else {\n yield put(loadSolutionCards(cards))\n }\n } catch (e) {\n // eslint-disable-next-line no-console\n console.error('Error loading solution cards', e)\n }\n}\n\nfunction* addSolutionCard(action) {\n try {\n const client = getClient()\n const response = yield client.mutate({\n mutation: gql`\n mutation createSolutionCard {\n CreateSolutioncard(value: {\n name: \"${action.payload.name}\"\n }) {\n name\n sid\n srn\n lastModified\n createdDate\n resourceId\n }\n }\n `,\n forceFetch: true,\n fetchPolicy: 'no-cache',\n })\n\n const newCard = response.data.CreateSolutioncard\n newCard.widgets = []\n\n yield put(\n push({\n search: qs.stringify({ tabId: newCard.srn }),\n })\n )\n\n yield put(addSolutionCardSuccess(newCard))\n } catch (e) {\n // eslint-disable-next-line no-console\n console.error('Error adding solution card', e)\n }\n}\n\nfunction* deleteSolutionCard(action) {\n try {\n const client = getClient()\n yield client.mutate({\n mutation: gql`\n mutation deleteSolutionCard {\n DeleteSolutioncard(srn: \"${action.payload.srn}\")\n }\n `,\n })\n yield put(deleteSolutionCardSuccess(action.payload.srn))\n } catch (e) {\n // eslint-disable-next-line no-console\n console.error('Error deleting solution card', e)\n }\n}\n\nfunction* addWidget(action) {\n try {\n let widg = Object.assign({}, action.payload.widget, {\n containedBy: {\n add: [action.payload.card.srn],\n },\n })\n\n delete widg.query\n const client = getClient()\n const result = yield client.mutate({\n mutation: gql`\n mutation addWidget($widget: WidgetCreator!) {\n CreateWidget(value: $widget) {\n sid\n srn\n label\n type\n icon\n title\n lastModified\n createdDate\n widgetSize\n subtitle\n selection\n resultLayout\n static\n options\n widgetLocation\n name\n resourceId\n contains {\n items {\n name\n sid\n srn\n }\n }\n }\n }\n `,\n variables: { widget: widg },\n })\n\n if (!result.data.CreateWidget) {\n throw new Error('received null in response to CreateWidget')\n }\n\n yield put(\n addWidgetSuccess({\n cardsrn: action.payload.card.srn,\n widget: result.data.CreateWidget,\n })\n )\n\n yield call(fetchSavedSearches, { poll: false })\n } catch (e) {\n // eslint-disable-next-line no-console\n console.error('Error adding widget to solution card', e)\n }\n}\n\nfunction* updateEditWidget(action) {\n const values = omit(action.payload.widget, [\n '__typename',\n 'query',\n 'sid',\n 'srn',\n 'lastModified',\n 'createdDate',\n 'contains',\n 'name',\n 'selection',\n 'resourceId',\n ])\n\n const { srn } = action.payload.widget\n\n try {\n const client = getClient()\n const updateResult = yield client.mutate({\n mutation: gql`\n mutation updateWidget($widget: WidgetUpdater!) {\n UpdateWidget(srn: \"${srn}\", value: $widget) {\n title\n subtitle\n name\n resultLayout\n static\n type\n widgetLocation\n createdDate \n sid\n srn\n lastModified\n widgetSize\n options\n selection\n resourceId\n contains {\n items {\n name\n sid\n }\n }\n }\n }\n `,\n variables: {\n widget: values,\n },\n })\n\n yield put(\n updateWidgetSuccess({\n cardsrn: action.payload.card.srn,\n widget: updateResult.data.UpdateWidget,\n })\n )\n } catch (e) {\n // eslint-disable-next-line no-console\n console.error('Error updating widget', e)\n }\n}\n\nfunction* removeWidget(action) {\n try {\n const client = getClient()\n yield client.mutate({\n mutation: gql`\n mutation delWidget{\n DeleteWidget(srn:\"${action.payload.srn}\")\n }\n `,\n })\n yield put(\n removeWidgetSuccess({\n cardId: action.payload.cardId,\n widgetId: action.payload.srn,\n })\n )\n } catch (e) {\n // eslint-disable-next-line no-console\n console.error('Error removing widget from solution card', e)\n }\n}\n\nfunction* saveLayout(action) {\n try {\n const widgets = action.payload.card.get('widgets')\n if (!widgets.isEmpty()) {\n const widgetsByIndex = Map(\n widgets.map((widget, index) => List([`widget${index}`, widget]))\n )\n\n const variables = widgetsByIndex\n .map(widget =>\n Map({\n widgetLocation: widget.get('widgetLocation'),\n static: widget.get('static'),\n widgetSize: widget.get('widgetSize'),\n options: widget.get('options'),\n })\n )\n .toJS()\n\n const variableNames = widgets.map(\n (widget, index) => `\n $widget${index}: WidgetUpdater!\n `\n )\n\n const queryStrings = widgets.map(\n (widget, index) => `\n widget${index}: UpdateWidget(\n srn: \"${widget.get('srn')}\",\n value: $widget${index}\n ) { sid srn }\n `\n )\n const client = getClient()\n yield client.mutate({\n mutation: gql`\n mutation updateWidgets(${variableNames.join(', ')}) {\n ${queryStrings.join(',')}\n }\n `,\n variables: variables,\n })\n }\n\n yield put(saveLayoutSuccess())\n } catch (e) {\n // eslint-disable-next-line no-console\n console.error('Error saving widget layout', e)\n }\n}\n\nfunction* updateSolutionCard(action) {\n try {\n const client = getClient()\n const updateResult = yield client.mutate({\n mutation: gql`\n mutation updateCard($card: SolutioncardUpdater!){\n UpdateSolutioncard(\n srn:\"${action.payload.srn}\"\n value: $card\n ) {\n srn,\n name,\n lastModified\n }\n }\n `,\n variables: {\n card: {\n name: action.payload.name,\n },\n },\n })\n\n yield put(updateSolutionCardSuccess(updateResult.data.UpdateSolutioncard))\n } catch (e) {\n // eslint-disable-next-line no-console\n console.error('Error editing solution card', e)\n }\n}\n\nfunction* copySolutionCard(action) {\n const { title, widgets } = action.payload\n\n try {\n const client = getClient()\n const newSolutionCard = yield client.mutate({\n mutation: gql`\n mutation createSolutionCard {\n CreateSolutioncard(value: {\n name: \"${title}\"\n }) {\n name\n sid\n srn\n lastModified\n createdDate\n resourceId\n }\n }\n `,\n })\n\n if (!newSolutionCard.data) {\n throw new Error('No copied solution card returned')\n }\n\n const srn = newSolutionCard.data.CreateSolutioncard.srn\n const widgetsJS = widgets.toJS()\n\n const newWidgets = yield all(\n widgetsJS.map(widg => {\n const contains = []\n widg.contains.items.forEach(item => {\n contains.push(item.srn)\n })\n const coolWidg = {\n title: widg.title,\n subtitle: '',\n type: widg.type,\n resultLayout: widg.resultLayout,\n widgetSize: widg.widgetSize,\n widgetLocation: widg.widgetLocation,\n static: widg.static,\n contains: { add: contains },\n options: widg.options,\n containedBy: { add: [srn] },\n }\n\n return client.mutate({\n mutation: gql`\n mutation addWidget($widget: WidgetCreator!) {\n CreateWidget(value: $widget) {\n sid\n srn\n label\n type\n icon\n title\n lastModified\n createdDate\n widgetSize\n subtitle\n selection\n resultLayout\n static\n options\n widgetLocation\n name\n resourceId\n contains {\n items {\n name\n sid\n srn\n }\n }\n }\n }\n `,\n variables: { widget: coolWidg },\n })\n })\n )\n\n const unpackedWidgets = []\n newWidgets.forEach(guy => {\n unpackedWidgets.push(guy.data.CreateWidget)\n })\n\n yield put(\n push({\n search: qs.stringify({\n tabId: newSolutionCard.data.CreateSolutioncard.srn,\n }),\n })\n )\n\n yield put(\n copySolutionCardSuccess({\n card: newSolutionCard.data.CreateSolutioncard,\n widgets: unpackedWidgets,\n })\n )\n } catch (e) {\n // eslint-disable-next-line no-console\n console.error('Error copying solution card', e)\n }\n}\n\nfunction* runSolutionCenterFetcher() {\n while (true) {\n yield call(getSolnCards)\n yield call(delay, 300000)\n }\n}\n\nfunction* mySaga() {\n yield all([\n takeLatest(GET_SOLUTION_CARDS, getSolnCards),\n takeLatest(HANDLE_SC_ADD, addSolutionCard),\n takeLatest(DELETE_SOLUTION_CARD, deleteSolutionCard),\n takeLatest(ADD_SC_WIDGET, addWidget),\n takeLatest(REMOVE_SC_WIDGET, removeWidget),\n takeLatest(SAVE_LAYOUT, saveLayout),\n takeLatest(UPDATE_EDIT_WIDGET, updateEditWidget),\n takeLatest(UPDATE_SOLUTION_CARD, updateSolutionCard),\n takeLatest(COPY_SOLUTION_CARD, copySolutionCard),\n ])\n\n while (true) {\n yield take(START_UPDATE_SC_POLL)\n yield race([call(runSolutionCenterFetcher), take(STOP_UPDATE_SC_POLL)])\n }\n}\n\nexport default mySaga\n","/*\n * SolutionCenter Messages\n *\n * This contains all the text for the SolutionCenter component.\n */\nimport { defineMessages } from 'react-intl'\n\nexport default defineMessages({\n identity: {\n id: 'app.container.SolutionCenter.header',\n defaultMessage: 'Identity',\n },\n infrastructure: {\n id: 'app.container.SolutionCenter.infrastructure',\n defaultMessage: 'Infrastructure',\n },\n data: {\n id: 'app.container.SolutionCenter.data',\n defaultMessage: 'Data',\n },\n cloud: {\n id: 'app.container.SolutionCenter.cloud',\n defaultMessage: 'Cloud',\n },\n header: {\n id: 'app.containers.SolutionCenter.header',\n defaultMessage: 'This is SolutionCenter container !',\n },\n createSC: {\n id: 'app.containers.SolutionCenter.createSC',\n defaultMessage: 'Create New Solution Card',\n },\n saveLayoutButton: {\n id: 'app.containers.SolutionCenter.saveLayoutButton',\n defaultMessage: 'Save Layout',\n },\n saveScEditButton: {\n id: 'app.containers.SolutionCenter.saveScEditButton',\n defaultMessage: 'Update',\n },\n cancelScEditButton: {\n id: 'app.containers.SolutionCenter.cancelScEditButton',\n defaultMessage: 'Cancel',\n },\n error: {\n id: 'app.containers.SolutionCenter.error',\n defaultMessage: \"Ooops...that wasn't supposed to happen!\",\n },\n errorContact: {\n id: 'app.containers.SolutionCenter.contact',\n defaultMessage:\n \"We'd love to hear about it, though! You can contact support at\",\n },\n statusDaysAll: {\n id: 'app.containers.SolutionCenterFilterStatus.statusDaysAll',\n defaultMessage: 'all time',\n },\n statusDays1: {\n id: 'app.containers.SolutionCenterFilterStatus.statusDays1',\n defaultMessage: 'the past 24 hours',\n },\n statusDays7: {\n id: 'app.containers.SolutionCenterFilterStatus.statusDays7',\n defaultMessage: 'the past 7 days',\n },\n statusDays30: {\n id: 'app.containers.SolutionCenterFilterStatus.statusDays30',\n defaultMessage: 'the past 30 days',\n },\n statusDays90: {\n id: 'app.containers.SolutionCenterFilterStatus.statusDays90',\n defaultMessage: 'the past 90 days',\n },\n filterApplied: {\n id: 'app.containers.SolutionCenterFilterStatus.filterApplied',\n defaultMessage: 'Filter Applied',\n },\n newWidget: {\n id: 'app.container.SolutionCenterToolbar.newWidget',\n defaultMessage: 'New Widget',\n },\n})\n","import React, { Fragment } from 'react'\nimport PropTypes from 'prop-types'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport { connect } from 'react-redux'\nimport { createStructuredSelector } from 'reselect'\nimport { compose, bindActionCreators } from 'redux'\nimport { Map } from 'immutable'\nimport { injectIntl } from 'react-intl'\nimport { selectShowCreateWidgetModal } from './selectors'\nimport { setShowCreateWidgetModal } from './actions'\nimport {\n selectSavingWidget,\n selectLayout,\n} from 'containers/SolutionCenterWidgets/selectors'\nimport { selectQueryTypes, selectPivot } from 'containers/SonraiData/selectors'\nimport Icon from 'components/Icon'\nimport { FormattedMessage } from 'react-intl'\nimport messages from './messages'\nimport WidgetModal from 'containers/WidgetModal'\nimport Button from 'components/Button'\nimport { QueryBuilder } from 'query-builder'\n\nconst styles = {\n button: {\n margin: '0.15rem 0rem 0rem 0.15rem',\n padding: '0.5rem',\n fontSize: '0.8rem',\n },\n}\n\nexport class SolutionCenterToolbar extends React.Component {\n toggleAddWidgetModal = () => {\n this.props.setShowCreateWidgetModal(!this.props.showCreateWidgetModal)\n }\n\n getQueryBuilder = fields => {\n const queryBuilder = new QueryBuilder({\n query: fields,\n types: this.props.queryTypes,\n pivot: this.props.pivot,\n })\n\n return queryBuilder\n }\n\n render() {\n return (\n \n \n \n \n )\n }\n}\n\nSolutionCenterToolbar.propTypes = {\n card: ImmutablePropTypes.contains({\n sid: PropTypes.string.isRequired,\n srn: PropTypes.string.isRequired,\n widgets: ImmutablePropTypes.listOf(\n ImmutablePropTypes.contains({\n sid: PropTypes.string.isRequired,\n title: PropTypes.string.isRequired,\n subtitle: PropTypes.string.isRequired,\n type: PropTypes.oneOf([\n 'bigCount',\n 'table',\n 'pieChart',\n 'lineChart',\n 'ratio',\n 'barChart',\n 'list',\n 'gauge',\n 'map',\n 'regions',\n 'advMap',\n 'compliance',\n 'spark',\n 'alert',\n ]).isRequired,\n options: ImmutablePropTypes.map,\n query: ImmutablePropTypes.map,\n selection: PropTypes.string,\n formatter: PropTypes.func,\n widgetSize: ImmutablePropTypes.list.isRequired,\n widgetLocation: ImmutablePropTypes.list.isRequired,\n static: PropTypes.bool.isRequired,\n })\n ),\n }).isRequired,\n savingWidget: PropTypes.bool,\n setShowCreateWidgetModal: PropTypes.func,\n showCreateWidgetModal: PropTypes.bool,\n layout: PropTypes.array.isRequired,\n pivot: ImmutablePropTypes.map.isRequired,\n queryTypes: ImmutablePropTypes.iterable.isRequired,\n}\n\nconst mapStateToProps = createStructuredSelector({\n savingWidget: selectSavingWidget,\n layout: selectLayout,\n pivot: selectPivot,\n queryTypes: selectQueryTypes,\n showCreateWidgetModal: selectShowCreateWidgetModal,\n})\n\nfunction mapDispatchToProps(dispatch) {\n return bindActionCreators({ setShowCreateWidgetModal }, dispatch)\n}\n\nconst withConnect = connect(\n mapStateToProps,\n mapDispatchToProps\n)\n\nexport default compose(\n withConnect,\n injectIntl\n)(SolutionCenterToolbar)\n","import React, { Component } from 'react'\nimport PropTypes from 'prop-types'\nimport { FormattedMessage } from 'react-intl'\n\nimport { SUPPORT_EMAIL } from 'appConstants'\nimport Error from 'components/Error'\nimport messages from './messages'\n\nexport default class SolutionCenterErrorBoundry extends Component {\n constructor(props) {\n super(props)\n this.state = {\n hasError: false,\n errorMessage: '',\n errorSource: '',\n componentStack: '',\n errorStack: '',\n }\n }\n\n componentDidCatch(error, info) {\n this.setState({\n hasError: true,\n errorMessage: error.message,\n errorStack: error.stack,\n componentStack: info.componentStack,\n errorSource: error.source ? error.source.body : '',\n })\n\n // eslint-disable-next-line no-console\n console.error({ json: JSON.stringify(this.state), ...this.state })\n }\n\n render() {\n if (this.state.hasError) {\n return (\n \n \n \n
\n \n {' '}\n {SUPPORT_EMAIL}\n
\n \n }\n errorMessage={this.state.errorMessage}\n />\n )\n }\n\n return this.props.children\n }\n}\n\nSolutionCenterErrorBoundry.propTypes = {\n children: PropTypes.node,\n}\n","import React from 'react'\nimport PropTypes from 'prop-types'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport { connect } from 'react-redux'\nimport { createStructuredSelector } from 'reselect'\nimport { compose } from 'redux'\nimport { injectIntl } from 'react-intl'\nimport { List } from 'immutable'\n\nimport CloudAccount from 'components/CloudAccount'\nimport { PIVOT_FILEDS } from 'query-builder'\nimport themeable, { themeShape } from 'containers/ThemeManager/Themeable'\nimport Icon from 'components/Icon'\nimport messages from './messages'\nimport { selectPivot } from 'containers/SonraiData/selectors'\n\nconst styles = {\n appliedFilter: {\n fontWeight: 400,\n },\n filterStatus: {\n textAlign: 'center',\n margin: 'auto',\n borderBottom: '1px solid #eee',\n padding: '1em 0',\n fontSize: '1em',\n },\n}\n\nexport class SolutionCenterFilterStatus extends React.Component {\n render() {\n if (this.props.pivot.isEmpty()) {\n return \n }\n\n const appliedFilters = this.props.pivot\n .map((value, key) => {\n if (key === PIVOT_FILEDS.RELATIVE_DATE) {\n switch (value) {\n case 1:\n return this.props.intl.formatMessage(messages.statusDays1)\n case 7:\n return this.props.intl.formatMessage(messages.statusDays7)\n case 30:\n return this.props.intl.formatMessage(messages.statusDays30)\n case 90:\n return this.props.intl.formatMessage(messages.statusDays90)\n }\n\n return null\n }\n\n if (key === PIVOT_FILEDS.ACCOUNTS) {\n const accounts = value.map(accountId => {\n return (\n \n \n
\n )\n })\n\n return (\n \n {key}: {accounts}\n
\n )\n }\n\n if (key === PIVOT_FILEDS.HAS_TAGS) {\n const tags = value || List()\n\n return `${key}: ${tags.toJS().join('; ')}`\n }\n\n if (key === PIVOT_FILEDS.SPECIFIC_DATES) {\n return `${key}: ${value.get('startDate')} - ${value.get('endDate')}`\n }\n\n if (List.isList(value)) {\n return `${key}: ${value.toJS().join('; ')}`\n }\n\n return `${key}: ${value}`\n })\n .toList()\n\n return (\n \n {' '}\n Filtering By {appliedFilters}\n
\n )\n }\n}\n\nSolutionCenterFilterStatus.propTypes = {\n intl: PropTypes.shape({\n formatMessage: PropTypes.func.isRequired,\n }).isRequired,\n pivot: ImmutablePropTypes.map,\n theme: themeShape,\n}\n\nconst mapStateToProps = createStructuredSelector({\n pivot: selectPivot,\n})\n\nconst withConnect = connect(mapStateToProps)\n\nexport default compose(\n withConnect,\n themeable,\n injectIntl\n)(SolutionCenterFilterStatus)\n","import React from 'react'\nimport PropTypes from 'prop-types'\nimport { connect } from 'react-redux'\nimport { createStructuredSelector } from 'reselect'\nimport { compose, bindActionCreators } from 'redux'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport { DAEMON } from 'utils/constants'\nimport LoadingAnim from 'components/LoadingAnim'\nimport injectReducer from 'utils/injectReducer'\nimport injectSaga from 'utils/injectSaga'\nimport { selectQueryTypes } from 'containers/SonraiData/selectors'\nimport SolutionCenterCards from 'containers/SolutionCenterCards'\nimport { selectSelectedCard } from 'containers/SolutionCenterCards/selectors'\nimport SolutionCenterWidgets from 'containers/SolutionCenterWidgets'\nimport SolutionCenterFilterPanel from 'containers/SolutionCenterFilterPanel'\nimport SolutionCenterCardActions from 'containers/SolutionCenterCardActions'\nimport WithPermission from 'containers/PermissionChecker/WithPermission'\nimport { selectCards } from './selectors'\nimport reducer from './reducer'\nimport { startUpdateScPoll, stopUpdateScPoll } from './actions'\nimport sagas from './sagas'\nimport SolutionCenterToolbar from './SolutionCenterToolbar'\nimport SolutionCenterErrorBoundry from './SolutionCenterErrorBoundry'\nimport SolutionCenterFilterStatus from './SolutionCenterFilterStatus'\nexport class SolutionCenter extends React.Component {\n constructor(props) {\n super(props)\n\n this.state = {\n error: false,\n showFiltersPanel: false,\n }\n\n this.styles = {\n wrapper: {\n height: '100%',\n display: 'grid',\n gridTemplateRows: 'auto 1fr',\n gridTemplateAreas: '\"toolbar\" \"content\"',\n position: 'relative',\n },\n toolbar: {\n gridArea: 'toolbar',\n },\n content: {\n gridArea: 'content',\n display: 'grid',\n gridTemplateRows: 'auto auto 1fr',\n gridTemplateAreas: '\"scToolbar\" \"widgets\"',\n overflow: 'hidden',\n },\n\n scToolbar: {\n gridArea: 'scToolbar',\n display: 'grid',\n gridTemplateColumns: 'auto 1fr auto',\n gridTemplateAreas: '\"add filterstatus cardActions\"',\n },\n filterStatus: {\n gridArea: 'filterstatus',\n },\n cardActions: {\n gridArea: 'cardActions',\n },\n widgets: {\n gridArea: 'widgets',\n overflow: 'auto',\n position: 'relative',\n zIndex: 0,\n },\n }\n }\n\n componentDidMount() {\n this.props.startUpdateScPoll()\n }\n\n componentDidCatch(error) {\n this.setState({ error })\n }\n\n componentWillUnmount() {\n this.props.stopUpdateScPoll()\n }\n\n toggleFiltersPanel = () => {\n this.setState(currentState => ({\n showFiltersPanel: !currentState.showFiltersPanel,\n }))\n }\n\n render() {\n if (this.state.error) {\n return \n }\n const dataLoaded =\n !this.props.solutionCards.isEmpty() && !this.props.queryTypes.isEmpty()\n\n if (!dataLoaded) {\n return \n }\n\n return (\n \n \n
\n {!this.props.selectedCard.isEmpty() && (\n
\n \n
\n )}\n
\n
\n \n \n
\n \n
\n
\n \n
\n
\n
\n\n
\n \n
\n
\n
\n \n )\n }\n}\n\nSolutionCenter.propTypes = {\n queryTypes: ImmutablePropTypes.iterable.isRequired,\n selectedCard: ImmutablePropTypes.map.isRequired,\n solutionCards: ImmutablePropTypes.iterable.isRequired,\n startUpdateScPoll: PropTypes.func.isRequired,\n stopUpdateScPoll: PropTypes.func.isRequired,\n}\n\nconst mapStateToProps = createStructuredSelector({\n selectedCard: selectSelectedCard,\n solutionCards: selectCards,\n queryTypes: selectQueryTypes,\n})\n\nfunction mapDispatchToProps(dispatch) {\n return bindActionCreators(\n {\n startUpdateScPoll,\n stopUpdateScPoll,\n },\n dispatch\n )\n}\n\nconst withConnect = connect(mapStateToProps, mapDispatchToProps)\n\nconst withReducer = injectReducer({ key: 'solutionCenter', reducer })\nconst withSaga = injectSaga({\n key: 'solutionCenter',\n saga: sagas,\n mode: DAEMON,\n})\n\nexport default compose(withReducer, withConnect, withSaga)(SolutionCenter)\n"],"sourceRoot":""}