n.length){for(c=0;cz(e,n,(r=>e[r][t]>=n)),t._scaleRangesChanged=function(e){const{xScale:t,yScale:n,_scaleRanges:r}=e,i={xmin:t.min,xmax:t.max,ymin:n.min,ymax:n.max};if(!r)return e._scaleRanges=i,!0;const o=r.xmin!==t.min||r.xmax!==t.max||r.ymin!==n.min||r.ymax!==n.max;return Object.assign(r,i),o},t._setMinAndMaxByKey=function(e,t,n){let r,i,o;for(r=0,i=e.length;r0?t.y:e.y}},t._steppedLineTo=function(e,t,n,r,i){if(!t)return e.lineTo(n.x,n.y);if("middle"===i){const r=(t.x+n.x)/2;e.lineTo(r,t.y),e.lineTo(r,n.y)}else"after"===i!=!!r?e.lineTo(t.x,n.y):e.lineTo(n.x,t.y);e.lineTo(n.x,n.y)},t._textX=(e,t,n,r)=>e===(r?"left":"right")?n:"center"===e?(t+n)/2:t,t._toLeftRightCenter=e=>"start"===e?"left":"end"===e?"right":"center",t._updateBezierControlPoints=function(e,t,n,r,i){let o,s,a,l;if(t.spanGaps&&(e=e.filter((e=>!e.skip))),"monotone"===t.cubicInterpolationMode)Ne(e,i);else{let n=r?e[e.length-1]:e[0];for(o=0,s=e.length;o=e},t.callback=function(e,t,n){if(e&&"function"==typeof e.call)return e.apply(n,t)},t.clearCanvas=function(e,t){(t||e)&&((t=t||e.getContext("2d")).save(),t.resetTransform(),t.clearRect(0,0,e.width,e.height),t.restore())},t.clipArea=function(e,t){e.save(),e.beginPath(),e.rect(t.left,t.top,t.right-t.left,t.bottom-t.top),e.clip()},t.clone=d,t.color=function(e){return K(e)?e:new r.Color(e)},t.createContext=ve,t.debounce=function(e,t){let n;return function(...r){return t?(clearTimeout(n),n=setTimeout(e,t,r)):e.apply(this,r),t}},t.defaults=se,t.defined=e=>void 0!==e,t.descriptors=ne,t.distanceBetweenPoints=M,t.drawPoint=function(e,t,n,r){ce(e,t,n,r,null)},t.drawPointLegend=ce,t.each=function(e,t,n,r){let i,o,l;if(s(e))if(o=e.length,r)for(i=o-1;i>=0;i--)t.call(n,e[i],i);else for(i=0;il.height&&(u=l.height,c=Ve(Math.floor(u*r))),{width:c,height:u}},t.getRelativePosition=function(e,t){if("native"in e)return e;const{canvas:n,currentDevicePixelRatio:r}=t,i=$e(n),o="border-box"===i.boxSizing,s=He(i,"padding"),a=He(i,"border","width"),{x:l,y:c,box:u}=function(e,t){const n=e.touches,r=n&&n.length?n[0]:e,{offsetX:i,offsetY:o}=r;let s,a,l=!1;if(((e,t,n)=>(e>0||t>0)&&(!n||!n.shadowRoot))(i,o,e.target))s=i,a=o;else{const e=t.getBoundingClientRect();s=r.clientX-e.left,a=r.clientY-e.top,l=!0}return{x:s,y:a,box:l}}(e,n),d=s.left+(u&&a.left),h=s.top+(u&&a.top);let{width:f,height:p}=t;return o&&(f-=s.width+a.width,p-=s.height+a.height),{x:Math.round((l-d)/f*n.width/r),y:Math.round((c-h)/p*n.height/r)}},t.getRtlAdapter=function(e,t,n){return e?function(e,t){return{x:n=>e+e+t-n,setWidth(e){t=e},textAlign:e=>"center"===e?e:"right"===e?"left":"right",xPlus:(e,t)=>e-t,leftForLtr:(e,t)=>e-t}}(t,n):{x:e=>e,setWidth(e){},textAlign:e=>e,xPlus:(e,t)=>e+t,leftForLtr:(e,t)=>e}},t.getStyle=Ue,t.isArray=s,t.isFunction=_,t.isNullOrUndef=o,t.isNumber=function(e){return!isNaN(parseFloat(e))&&isFinite(e)},t.isNumberFinite=l,t.isObject=a,t.isPatternOrGradient=K,t.listenArrayEvents=function(e,t){e._chartjs?e._chartjs.listeners.push(t):(Object.defineProperty(e,"_chartjs",{configurable:!0,enumerable:!1,value:{listeners:[t]}}),$.forEach((t=>{const n="_onData"+A(t),r=e[t];Object.defineProperty(e,t,{configurable:!0,enumerable:!1,value(...t){const i=r.apply(this,t);return e._chartjs.listeners.forEach((e=>{"function"==typeof e[n]&&e[n](...t)})),i}})})))},t.log10=P,t.merge=p,t.mergeIf=g,t.niceNum=function(e){const t=Math.round(e);e=j(e,t,e/1e3)?t:e;const n=Math.pow(10,Math.floor(P(e))),r=e/n;return(r<=1?1:r<=2?2:r<=5?5:10)*n},t.noop=function(){},t.overrideTextDirection=function(e,t){let n,r;"ltr"!==t&&"rtl"!==t||(n=e.canvas.style,r=[n.getPropertyValue("direction"),n.getPropertyPriority("direction")],n.setProperty("direction",t,"important"),e.prevTextDirection=r)},t.overrides=te,t.readUsedSize=function(e,t){const n=Ue(e,t),r=n&&n.match(/^(\d+)(\.\d+)?px$/);return r?+r[1]:void 0},t.renderText=function(e,t,n,r,i,a={}){const l=s(t)?t:[t],c=a.strokeWidth>0&&""!==a.strokeColor;let u,d;for(e.save(),e.font=i.string,function(e,t){t.translation&&e.translate(t.translation[0],t.translation[1]),o(t.rotation)||e.rotate(t.rotation),t.color&&(e.fillStyle=t.color),t.textAlign&&(e.textAlign=t.textAlign),t.textBaseline&&(e.textBaseline=t.textBaseline)}(e,a),u=0;u{if(e.size!==t.size)return!1;for(const n of e)if(!t.has(n))return!1;return!0},t.sign=R,t.splineCurve=Le,t.splineCurveMonotone=Ne,t.supportsEventListenerOptions=qe,t.throttled=function(e,t){let n=[],r=!1;return function(...i){n=i,r||(r=!0,U.call(window,(()=>{r=!1,e.apply(t,n)})))}},t.toDegrees=function(e){return e*(180/w)},t.toDimension=u,t.toFont=function(e,t){e=e||{},t=t||se.font;let n=c(e.size,t.size);"string"==typeof n&&(n=parseInt(n,10));let r=c(e.style,t.style);r&&!(""+r).match(pe)&&(console.warn('Invalid font style specified: "'+r+'"'),r=void 0);const i={family:c(e.family,t.family),lineHeight:ge(c(e.lineHeight,t.lineHeight),n),size:n,style:r,weight:c(e.weight,t.weight),string:""};return i.string=ae(i),i},t.toFontString=ae,t.toLineHeight=ge,t.toPadding=function(e){const t=ye(e);return t.width=t.left+t.right,t.height=t.top+t.bottom,t},t.toPercentage=(e,t)=>"string"==typeof e&&e.endsWith("%")?parseFloat(e)/100:+e/t,t.toRadians=function(e){return e*(w/180)},t.toTRBL=ye,t.toTRBLCorners=function(e){return be(e,["topLeft","topRight","bottomLeft","bottomRight"])},t.uid=i,t.unclipArea=function(e){e.restore()},t.unlistenArrayEvents=function(e,t){const n=e._chartjs;if(!n)return;const r=n.listeners,i=r.indexOf(t);-1!==i&&r.splice(i,1),r.length>0||($.forEach((t=>{delete e[t]})),delete e._chartjs)},t.valueOrDefault=c},6942:(e,t)=>{var n;!function(){"use strict";var r={}.hasOwnProperty;function i(){for(var e="",t=0;t{"use strict";var r=n(6540),i=n(8969);const o="label";function s(e,t){"function"==typeof e?e(t):e&&(e.current=t)}function a(e,t){e.labels=t}function l(e,t){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:o;const r=[];e.datasets=t.map((t=>{const i=e.datasets.find((e=>e[n]===t[n]));return i&&t.data&&!r.includes(i)?(r.push(i),Object.assign(i,t),i):{...t}}))}function c(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:o;const n={labels:[],datasets:[]};return a(n,e.labels),l(n,e.datasets,t),n}function u(e,t){const{height:n=150,width:o=300,redraw:u=!1,datasetIdKey:d,type:h,data:f,options:p,plugins:g=[],fallbackContent:m,updateMode:b,...y}=e,v=r.useRef(null),A=r.useRef(),_=()=>{v.current&&(A.current=new i.Chart(v.current,{type:h,data:c(f,d),options:p&&{...p},plugins:g}),s(t,A.current))},w=()=>{s(t,null),A.current&&(A.current.destroy(),A.current=null)};return r.useEffect((()=>{!u&&A.current&&p&&function(e,t){const n=e.options;n&&t&&Object.assign(n,t)}(A.current,p)}),[u,p]),r.useEffect((()=>{!u&&A.current&&a(A.current.config.data,f.labels)}),[u,f.labels]),r.useEffect((()=>{!u&&A.current&&f.datasets&&l(A.current.config.data,f.datasets,d)}),[u,f.datasets]),r.useEffect((()=>{A.current&&(u?(w(),setTimeout(_)):A.current.update(b))}),[u,p,f.labels,f.datasets,b]),r.useEffect((()=>{A.current&&(w(),setTimeout(_))}),[h]),r.useEffect((()=>(_(),()=>w())),[]),r.createElement("canvas",Object.assign({ref:v,role:"img",height:n,width:o},y),m)}const d=r.forwardRef(u);function h(e,t){return i.Chart.register(t),r.forwardRef(((t,n)=>r.createElement(d,Object.assign({},t,{ref:n,type:e}))))}const f=h("line",i.LineController),p=h("bar",i.BarController),g=h("radar",i.RadarController),m=h("doughnut",i.DoughnutController),b=h("polarArea",i.PolarAreaController),y=h("bubble",i.BubbleController),v=h("pie",i.PieController),A=h("scatter",i.ScatterController);t.Bar=p,t.Bubble=y,t.Chart=d,t.Doughnut=m,t.Line=f,t.Pie=v,t.PolarArea=b,t.Radar=g,t.Scatter=A,t.getDatasetAtEvent=function(e,t){return e.getElementsAtEventForMode(t.nativeEvent,"dataset",{intersect:!0},!1)},t.getElementAtEvent=function(e,t){return e.getElementsAtEventForMode(t.nativeEvent,"nearest",{intersect:!0},!1)},t.getElementsAtEvent=function(e,t){return e.getElementsAtEventForMode(t.nativeEvent,"index",{intersect:!0},!1)}},7372:(e,t,n)=>{"use strict";var r,i=Object.create,o=Object.defineProperty,s=Object.getOwnPropertyDescriptor,a=Object.getOwnPropertyNames,l=Object.getPrototypeOf,c=Object.prototype.hasOwnProperty,u=(e,t,n,r)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let i of a(t))!c.call(e,i)&&i!==n&&o(e,i,{get:()=>t[i],enumerable:!(r=s(t,i))||r.enumerable});return e},d=(e,t,n)=>(n=null!=e?i(l(e)):{},u(!t&&e&&e.__esModule?n:o(n,"default",{value:e,enumerable:!0}),e)),h={};((e,t)=>{for(var n in t)o(e,n,{get:t[n],enumerable:!0})})(h,{Provider:()=>ve,ReactReduxContext:()=>v,batch:()=>ke,connect:()=>ye,createDispatchHook:()=>we,createSelectorHook:()=>E,createStoreHook:()=>Ae,shallowEqual:()=>te,useDispatch:()=>xe,useSelector:()=>S,useStore:()=>_e}),e.exports=(r=h,u(o({},"__esModule",{value:!0}),r));var f=d(n(6540)),p=n(8418),g=d(n(6540)),m="default"in g?g.default:g,b=Symbol.for("react-redux-context"),y=typeof globalThis<"u"?globalThis:{},v=function(){if(!m.createContext)return{};let e=y[b]??(y[b]=new Map),t=e.get(m.createContext);return t||(t=m.createContext(null),e.set(m.createContext,t)),t}(),A=()=>{throw new Error("uSES not initialized!")};function _(e=v){return function(){return m.useContext(e)}}var w=_(),x=A,k=(e,t)=>e===t;function E(e=v){let t=e===v?w:_(e),n=(e,n={})=>{let{equalityFn:r=k,devModeChecks:i={}}="function"==typeof n?{equalityFn:n}:n,{store:o,subscription:s,getServerState:a,stabilityCheck:l,identityFunctionCheck:c}=t(),u=(m.useRef(!0),m.useCallback({[e.name]:t=>e(t)}[e.name],[e,l,i.stabilityCheck])),d=x(s.addNestedSub,o.getState,a||o.getState,u,r);return m.useDebugValue(d),d};return Object.assign(n,{withTypes:()=>n}),n}var S=E(),O=Symbol.for("react.element"),C=Symbol.for("react.portal"),T=Symbol.for("react.fragment"),P=Symbol.for("react.strict_mode"),R=Symbol.for("react.profiler"),j=Symbol.for("react.provider"),M=Symbol.for("react.context"),D=Symbol.for("react.server_context"),L=Symbol.for("react.forward_ref"),N=Symbol.for("react.suspense"),I=Symbol.for("react.suspense_list"),F=Symbol.for("react.memo"),z=Symbol.for("react.lazy"),B=(Symbol.for("react.offscreen"),Symbol.for("react.client.reference"),L),$=F;function U(e,t,n,r,{areStatesEqual:i,areOwnPropsEqual:o,areStatePropsEqual:s}){let a,l,c,u,d,h=!1;return function(f,p){return h?function(h,f){let p=!o(f,l),g=!i(h,a,f,l);return a=h,l=f,p&&g?(c=e(a,l),t.dependsOnOwnProps&&(u=t(r,l)),d=n(c,u,l),d):p?(e.dependsOnOwnProps&&(c=e(a,l)),t.dependsOnOwnProps&&(u=t(r,l)),d=n(c,u,l),d):g?function(){let t=e(a,l),r=!s(t,c);return c=t,r&&(d=n(c,u,l)),d}():d}(f,p):function(i,o){return a=i,l=o,c=e(a,l),u=t(r,l),d=n(c,u,l),h=!0,d}(f,p)}}function W(e){return function(t){let n=e(t);function r(){return n}return r.dependsOnOwnProps=!1,r}}function H(e){return e.dependsOnOwnProps?!!e.dependsOnOwnProps:1!==e.length}function V(e,t){return function(t,{displayName:n}){let r=function(e,t){return r.dependsOnOwnProps?r.mapToProps(e,t):r.mapToProps(e,void 0)};return r.dependsOnOwnProps=!0,r.mapToProps=function(t,n){r.mapToProps=e,r.dependsOnOwnProps=H(e);let i=r(t,n);return"function"==typeof i&&(r.mapToProps=i,r.dependsOnOwnProps=H(i),i=r(t,n)),i},r}}function q(e,t){return(n,r)=>{throw new Error(`Invalid value of type ${typeof e} for ${t} argument when connecting component ${r.wrappedComponentName}.`)}}function K(e,t,n){return{...n,...e,...t}}function Q(e){e()}var Y={notify(){},get:()=>[]};function G(e,t){let n,r=Y,i=0,o=!1;function s(){c.onStateChange&&c.onStateChange()}function a(){i++,n||(n=t?t.addNestedSub(s):e.subscribe(s),r=function(){let e=null,t=null;return{clear(){e=null,t=null},notify(){Q((()=>{let t=e;for(;t;)t.callback(),t=t.next}))},get(){let t=[],n=e;for(;n;)t.push(n),n=n.next;return t},subscribe(n){let r=!0,i=t={callback:n,next:null,prev:t};return i.prev?i.prev.next=i:e=i,function(){!r||null===e||(r=!1,i.next?i.next.prev=i.prev:t=i.prev,i.prev?i.prev.next=i.next:e=i.next)}}}}())}function l(){i--,n&&0===i&&(n(),n=void 0,r.clear(),r=Y)}let c={addNestedSub:function(e){a();let t=r.subscribe(e),n=!1;return()=>{n||(n=!0,t(),l())}},notifyNestedSubs:function(){r.notify()},handleChangeWrapper:s,isSubscribed:function(){return o},trySubscribe:function(){o||(o=!0,a())},tryUnsubscribe:function(){o&&(o=!1,l())},getListeners:()=>r};return c}var J=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u",X=typeof navigator<"u"&&"ReactNative"===navigator.product,Z=J||X?m.useLayoutEffect:m.useEffect;function ee(e,t){return e===t?0!==e||0!==t||1/e==1/t:e!=e&&t!=t}function te(e,t){if(ee(e,t))return!0;if("object"!=typeof e||null===e||"object"!=typeof t||null===t)return!1;let n=Object.keys(e),r=Object.keys(t);if(n.length!==r.length)return!1;for(let r=0;r({})))}(e),h=function(e){return e&&"object"==typeof e?W((t=>function(e,t){let n={};for(let r in e){let i=e[r];"function"==typeof i&&(n[r]=(...e)=>t(i(...e)))}return n}(e,t))):e?"function"==typeof e?V(e):q(e,"mapDispatchToProps"):W((e=>({dispatch:e})))}(t),f=function(e){return e?"function"==typeof e?function(e){return function(t,{displayName:n,areMergedPropsEqual:r}){let i,o=!1;return function(t,n,s){let a=e(t,n,s);return o?r(a,i)||(i=a):(o=!0,i=a),i}}}(e):q(e,"mergeProps"):()=>K}(n),p=!!e;return e=>{let t=e.displayName||e.name||"Component",n=`Connect(${t})`,r={shouldHandleStateChanges:p,displayName:n,wrappedComponentName:t,WrappedComponent:e,initMapStateToProps:d,initMapDispatchToProps:h,initMergeProps:f,areStatesEqual:i,areStatePropsEqual:s,areOwnPropsEqual:o,areMergedPropsEqual:a};function c(t){let[n,i,o]=m.useMemo((()=>{let{reactReduxForwardedRef:e,...n}=t;return[t.context,e,n]}),[t]),s=m.useMemo((()=>u),[n,u]),a=m.useContext(s),l=!!t.store&&!!t.store.getState&&!!t.store.dispatch,c=!!a&&!!a.store,d=l?t.store:a.store,h=c?a.getServerState:d.getState,f=m.useMemo((()=>function(e,{initMapStateToProps:t,initMapDispatchToProps:n,initMergeProps:r,...i}){return U(t(e,i),n(e,i),r(e,i),e,i)}(d.dispatch,r)),[d]),[g,b]=m.useMemo((()=>{if(!p)return ge;let e=G(d,l?void 0:a.subscription),t=e.notifyNestedSubs.bind(e);return[e,t]}),[d,l,a]),y=m.useMemo((()=>l?a:{...a,subscription:g}),[l,a,g]),v=m.useRef(void 0),A=m.useRef(o),_=m.useRef(void 0),w=m.useRef(!1),x=m.useRef(!1),k=m.useRef(void 0);Z((()=>(x.current=!0,()=>{x.current=!1})),[]);let E,S=m.useMemo((()=>()=>_.current&&o===A.current?_.current:f(d.getState(),o)),[d,o]),O=m.useMemo((()=>e=>g?function(e,t,n,r,i,o,s,a,l,c,u){if(!e)return()=>{};let d=!1,h=null,f=()=>{if(d||!a.current)return;let e,n,f=t.getState();try{e=r(f,i.current)}catch(e){n=e,h=e}n||(h=null),e===o.current?s.current||c():(o.current=e,l.current=e,s.current=!0,u())};return n.onStateChange=f,n.trySubscribe(),f(),()=>{if(d=!0,n.tryUnsubscribe(),n.onStateChange=null,h)throw h}}(p,d,g,f,A,v,w,x,_,b,e):()=>{}),[g]);!function(e,t,n){Z((()=>e(...t)),void 0)}(me,[A,v,w,o,_,b]);try{E=pe(O,S,h?()=>f(h(),o):S)}catch(e){throw k.current&&(e.message+=`\nThe error may be correlated with this previous error:\n${k.current.stack}\n\n`),e}Z((()=>{k.current=void 0,_.current=void 0,v.current=E}));let C=m.useMemo((()=>m.createElement(e,{...E,ref:i})),[i,e,E]);return m.useMemo((()=>p?m.createElement(s.Provider,{value:y},C):C),[s,C,y])}let g=m.memo(c);if(g.WrappedComponent=e,g.displayName=c.displayName=n,l){let t=m.forwardRef((function(e,t){return m.createElement(g,{...e,reactReduxForwardedRef:t})}));return t.displayName=n,t.WrappedComponent=e,fe(t,e)}return fe(g,e)}},ve=function({store:e,context:t,children:n,serverState:r,stabilityCheck:i="once",identityFunctionCheck:o="once"}){let s=m.useMemo((()=>{let t=G(e);return{store:e,subscription:t,getServerState:r?()=>r:void 0,stabilityCheck:i,identityFunctionCheck:o}}),[e,r,i,o]),a=m.useMemo((()=>e.getState()),[e]);return Z((()=>{let{subscription:t}=s;return t.onStateChange=t.notifyNestedSubs,t.trySubscribe(),a!==e.getState()&&t.notifyNestedSubs(),()=>{t.tryUnsubscribe(),t.onStateChange=void 0}}),[s,a]),m.createElement((t||v).Provider,{value:s},n)};function Ae(e=v){let t=e===v?w:_(e),n=()=>{let{store:e}=t();return e};return Object.assign(n,{withTypes:()=>n}),n}var _e=Ae();function we(e=v){let t=e===v?_e:Ae(e),n=()=>t().dispatch;return Object.assign(n,{withTypes:()=>n}),n}var xe=we(),ke=Q;(e=>{x=e})(p.useSyncExternalStoreWithSelector),(e=>{pe=e})(f.useSyncExternalStore)},8895:e=>{"use strict";var t,n=Object.defineProperty,r=Object.getOwnPropertyDescriptor,i=Object.getOwnPropertyNames,o=Object.prototype.hasOwnProperty,s={};function a(e){return`Minified Redux error #${e}; visit https://redux.js.org/Errors?code=${e} for the full message or use the non-minified dev environment for full errors. `}((e,t)=>{for(var r in t)n(e,r,{get:t[r],enumerable:!0})})(s,{__DO_NOT_USE__ActionTypes:()=>u,applyMiddleware:()=>y,bindActionCreators:()=>m,combineReducers:()=>p,compose:()=>b,createStore:()=>h,isAction:()=>v,isPlainObject:()=>d,legacy_createStore:()=>f}),e.exports=(t=s,((e,t,s,a)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let s of i(t))o.call(e,s)||undefined===s||n(e,s,{get:()=>t[s],enumerable:!(a=r(t,s))||a.enumerable});return e})(n({},"__esModule",{value:!0}),t));var l=(()=>"function"==typeof Symbol&&Symbol.observable||"@@observable")(),c=()=>Math.random().toString(36).substring(7).split("").join("."),u={INIT:`@@redux/INIT${c()}`,REPLACE:`@@redux/REPLACE${c()}`,PROBE_UNKNOWN_ACTION:()=>`@@redux/PROBE_UNKNOWN_ACTION${c()}`};function d(e){if("object"!=typeof e||null===e)return!1;let t=e;for(;null!==Object.getPrototypeOf(t);)t=Object.getPrototypeOf(t);return Object.getPrototypeOf(e)===t||null===Object.getPrototypeOf(e)}function h(e,t,n){if("function"!=typeof e)throw new Error(a(2));if("function"==typeof t&&"function"==typeof n||"function"==typeof n&&"function"==typeof arguments[3])throw new Error(a(0));if("function"==typeof t&&void 0===n&&(n=t,t=void 0),void 0!==n){if("function"!=typeof n)throw new Error(a(1));return n(h)(e,t)}let r=e,i=t,o=new Map,s=o,c=0,f=!1;function p(){s===o&&(s=new Map,o.forEach(((e,t)=>{s.set(t,e)})))}function g(){if(f)throw new Error(a(3));return i}function m(e){if("function"!=typeof e)throw new Error(a(4));if(f)throw new Error(a(5));let t=!0;p();const n=c++;return s.set(n,e),function(){if(t){if(f)throw new Error(a(6));t=!1,p(),s.delete(n),o=null}}}function b(e){if(!d(e))throw new Error(a(7));if(void 0===e.type)throw new Error(a(8));if("string"!=typeof e.type)throw new Error(a(17));if(f)throw new Error(a(9));try{f=!0,i=r(i,e)}finally{f=!1}return(o=s).forEach((e=>{e()})),e}return b({type:u.INIT}),{dispatch:b,subscribe:m,getState:g,replaceReducer:function(e){if("function"!=typeof e)throw new Error(a(10));r=e,b({type:u.REPLACE})},[l]:function(){const e=m;return{subscribe(t){if("object"!=typeof t||null===t)throw new Error(a(11));function n(){const e=t;e.next&&e.next(g())}return n(),{unsubscribe:e(n)}},[l](){return this}}}}}function f(e,t,n){return h(e,t,n)}function p(e){const t=Object.keys(e),n={};for(let r=0;r{const n=e[t];if(void 0===n(void 0,{type:u.INIT}))throw new Error(a(12));if(void 0===n(void 0,{type:u.PROBE_UNKNOWN_ACTION()}))throw new Error(a(13))}))}(n)}catch(e){i=e}return function(e={},t){if(i)throw i;let o=!1;const s={};for(let i=0;ie:1===e.length?e[0]:e.reduce(((e,t)=>(...n)=>e(t(...n))))}function y(...e){return t=>(n,r)=>{const i=t(n,r);let o=()=>{throw new Error(a(15))};const s={getState:i.getState,dispatch:(e,...t)=>o(e,...t)},l=e.map((e=>e(s)));return o=b(...l)(i.dispatch),{...i,dispatch:o}}}function v(e){return d(e)&&"type"in e&&"string"==typeof e.type}},2885:e=>{"use strict";var t,n=Object.defineProperty,r=Object.getOwnPropertyDescriptor,i=Object.getOwnPropertyNames,o=Object.prototype.hasOwnProperty,s={};((e,t)=>{for(var r in t)n(e,r,{get:t[r],enumerable:!0})})(s,{createSelector:()=>U,createSelectorCreator:()=>$,createStructuredSelector:()=>W,lruMemoize:()=>D,referenceEqualityCheck:()=>j,setGlobalDevModeChecks:()=>l,unstable_autotrackMemoize:()=>L,weakMapMemoize:()=>B}),e.exports=(t=s,((e,t,s,a)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let s of i(t))o.call(e,s)||undefined===s||n(e,s,{get:()=>t[s],enumerable:!(a=r(t,s))||a.enumerable});return e})(n({},"__esModule",{value:!0}),t));var a={inputStabilityCheck:"once",identityFunctionCheck:"once"},l=e=>{Object.assign(a,e)},c=Symbol("NOT_FOUND");function u(e,t="expected a function, instead received "+typeof e){if("function"!=typeof e)throw new TypeError(t)}var d=e=>Array.isArray(e)?e:[e];var h=0,f=null,p=class{revision=h;_value;_lastValue;_isEqual=g;constructor(e,t=g){this._value=this._lastValue=e,this._isEqual=t}get value(){return f?.add(this),this._value}set value(e){this.value!==e&&(this._value=e,this.revision=++h)}};function g(e,t){return e===t}var m=class{_cachedValue;_cachedRevision=-1;_deps=[];hits=0;fn;constructor(e){this.fn=e}clear(){this._cachedValue=void 0,this._cachedRevision=-1,this._deps=[],this.hits=0}get value(){if(this.revision>this._cachedRevision){const{fn:e}=this,t=new Set,n=f;f=t,this._cachedValue=e(),f=n,this.hits++,this._deps=Array.from(t),this._cachedRevision=this.revision}return f?.add(this),this._cachedValue}get revision(){return Math.max(...this._deps.map((e=>e.revision)),0)}};function b(e){return e instanceof p||console.warn("Not a valid cell! ",e),e.value}var y=(e,t)=>!1;function v(){return function(e,t=g){return new p(null,t)}(0,y)}function A(e,t){!function(e,t){if(!(e instanceof p))throw new TypeError("setValue must be passed a tracked store created with `createStorage`.");e.value=e._lastValue=t}(e,t)}var _=e=>{let t=e.collectionTag;null===t&&(t=e.collectionTag=v()),b(t)},w=e=>{const t=e.collectionTag;null!==t&&A(t,null)},x=(Symbol(),0),k=Object.getPrototypeOf({}),E=class{constructor(e){this.value=e,this.value=e,this.tag.value=e}proxy=new Proxy(this,S);tag=v();tags={};children={};collectionTag=null;id=x++},S={get:(e,t)=>function(){const{value:n}=e,r=Reflect.get(n,t);if("symbol"==typeof t)return r;if(t in k)return r;if("object"==typeof r&&null!==r){let n=e.children[t];return void 0===n&&(n=e.children[t]=T(r)),n.tag&&b(n.tag),n.proxy}{let n=e.tags[t];return void 0===n&&(n=e.tags[t]=v(),n.value=r),b(n),r}}(),ownKeys:e=>(_(e),Reflect.ownKeys(e.value)),getOwnPropertyDescriptor:(e,t)=>Reflect.getOwnPropertyDescriptor(e.value,t),has:(e,t)=>Reflect.has(e.value,t)},O=class{constructor(e){this.value=e,this.value=e,this.tag.value=e}proxy=new Proxy([this],C);tag=v();tags={};children={};collectionTag=null;id=x++},C={get:([e],t)=>("length"===t&&_(e),S.get(e,t)),ownKeys:([e])=>S.ownKeys(e),getOwnPropertyDescriptor:([e],t)=>S.getOwnPropertyDescriptor(e,t),has:([e],t)=>S.has(e,t)};function T(e){return Array.isArray(e)?new O(e):new E(e)}function P(e,t){const{value:n,tags:r,children:i}=e;if(e.value=t,Array.isArray(n)&&Array.isArray(t)&&n.length!==t.length)w(e);else if(n!==t){let r=0,i=0,o=!1;for(const e in n)r++;for(const e in t)if(i++,!(e in n)){o=!0;break}(o||r!==i)&&w(e)}for(const i in r){const o=n[i],s=t[i];o!==s&&(w(e),A(r[i],s)),"object"==typeof s&&null!==s&&delete r[i]}for(const e in i){const n=i[e],r=t[e];n.value!==r&&("object"==typeof r&&null!==r?P(n,r):(R(n),delete i[e]))}}function R(e){e.tag&&A(e.tag,null),w(e);for(const t in e.tags)A(e.tags[t],null);for(const t in e.children)R(e.children[t])}var j=(e,t)=>e===t;function M(e){return function(t,n){if(null===t||null===n||t.length!==n.length)return!1;const{length:r}=t;for(let i=0;it&&e(t.key,n)?t.value:c,put(e,n){t={key:e,value:n}},getEntries:()=>t?[t]:[],clear(){t=void 0}}}(s):function(e,t){let n=[];function r(e){const r=n.findIndex((n=>t(e,n.key)));if(r>-1){const e=n[r];return r>0&&(n.splice(r,1),n.unshift(e)),e.value}return c}return{get:r,put:function(t,i){r(t)===c&&(n.unshift({key:t,value:i}),n.length>e&&n.pop())},getEntries:function(){return n},clear:function(){n=[]}}}(i,s);function u(){let t=l.get(arguments);if(t===c){if(t=e.apply(null,arguments),a++,o){const e=l.getEntries().find((e=>o(e.value,t)));e&&(t=e.value,0!==a&&a--)}l.put(arguments,t)}return t}return u.clearCache=()=>{l.clear(),u.resetResultsCount()},u.resultsCount=()=>a,u.resetResultsCount=()=>{a=0},u}function L(e){const t=T([]);let n=null;const r=M(j),i=(u(o=()=>e.apply(null,t.proxy),"the first parameter to `createCache` must be a function"),new m(o));var o;function s(){return r(n,arguments)||(P(t,arguments),n=arguments),i.value}return s.clearCache=()=>i.clear(),s}var N="undefined"!=typeof WeakRef?WeakRef:class{constructor(e){this.value=e}deref(){return this.value}},I=0,F=1;function z(){return{s:I,v:void 0,o:null,p:null}}function B(e,t={}){let n=z();const{resultEqualityCheck:r}=t;let i,o=0;function s(){let t=n;const{length:s}=arguments;for(let e=0,n=s;e{n=z(),s.resetResultsCount()},s.resultsCount=()=>o,s.resetResultsCount=()=>{o=0},s}function $(e,...t){const n="function"==typeof e?{memoize:e,memoizeOptions:t}:e,r=(...e)=>{let t,r=0,i=0,o={},s=e.pop();"object"==typeof s&&(o=s,s=e.pop()),u(s,`createSelector expects an output function after the inputs, but received: [${typeof s}]`);const a={...n,...o},{memoize:l,memoizeOptions:c=[],argsMemoize:h=B,argsMemoizeOptions:f=[],devModeChecks:p={}}=a,g=d(c),m=d(f),b=function(e){const t=Array.isArray(e[0])?e[0]:e;return function(e,t="expected all items to be functions, instead received the following types: "){if(!e.every((e=>"function"==typeof e))){const n=e.map((e=>"function"==typeof e?`function ${e.name||"unnamed"}()`:typeof e)).join(", ");throw new TypeError(`${t}[${n}]`)}}(t,"createSelector expects all input-selectors to be functions, but received the following types: "),t}(e),y=l((function(){return r++,s.apply(null,arguments)}),...g),v=h((function(){i++;const e=function(e,t){const n=[],{length:r}=e;for(let i=0;ii,resetDependencyRecomputations:()=>{i=0},lastResult:()=>t,recomputations:()=>r,resetRecomputations:()=>{r=0},memoize:l,argsMemoize:h})};return Object.assign(r,{withTypes:()=>r}),r}var U=$(B),W=Object.assign(((e,t=U)=>{!function(e,t="expected an object, instead received "+typeof e){if("object"!=typeof e)throw new TypeError(t)}(e,"createStructuredSelector expects first argument to be an object where each property is a selector, instead received a "+typeof e);const n=Object.keys(e);return t(n.map((t=>e[t])),((...e)=>e.reduce(((e,t,r)=>(e[n[r]]=t,e)),{})))}),{withTypes:()=>W})},8587:(e,t,n)=>{"use strict";function r(e,t){if(null==e)return{};var n={};for(var r in e)if({}.hasOwnProperty.call(e,r)){if(t.indexOf(r)>=0)continue;n[r]=e[r]}return n}n.d(t,{A:()=>r})}},o={};function s(e){var t=o[e];if(void 0!==t)return t.exports;var n=o[e]={exports:{}};return i[e].call(n.exports,n,n.exports,s),n.exports}s.m=i,s.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return s.d(t,{a:t}),t},t=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,s.t=function(n,r){if(1&r&&(n=this(n)),8&r)return n;if("object"==typeof n&&n){if(4&r&&n.__esModule)return n;if(16&r&&"function"==typeof n.then)return n}var i=Object.create(null);s.r(i);var o={};e=e||[null,t({}),t([]),t(t)];for(var a=2&r&&n;"object"==typeof a&&!~e.indexOf(a);a=t(a))Object.getOwnPropertyNames(a).forEach((e=>o[e]=()=>n[e]));return o.default=()=>n,s.d(i,o),i},s.d=(e,t)=>{for(var n in t)s.o(t,n)&&!s.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},s.f={},s.e=e=>Promise.all(Object.keys(s.f).reduce(((t,n)=>(s.f[n](e,t),t)),[])),s.u=e=>e+".js",s.miniCssF=e=>{},s.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),s.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),n={},r="flake-guard:",s.l=(e,t,i,o)=>{if(n[e])n[e].push(t);else{var a,l;if(void 0!==i)for(var c=document.getElementsByTagName("script"),u=0;u{a.onerror=a.onload=null,clearTimeout(f);var i=n[e];if(delete n[e],a.parentNode&&a.parentNode.removeChild(a),i&&i.forEach((e=>e(r))),t)return t(r)},f=setTimeout(h.bind(null,void 0,{type:"timeout",target:a}),12e4);a.onerror=h.bind(null,a.onerror),a.onload=h.bind(null,a.onload),l&&document.head.appendChild(a)}},s.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{var e;s.g.importScripts&&(e=s.g.location+"");var t=s.g.document;if(!e&&t&&(t.currentScript&&(e=t.currentScript.src),!e)){var n=t.getElementsByTagName("script");if(n.length)for(var r=n.length-1;r>-1&&(!e||!/^http(s?):/.test(e));)e=n[r--].src}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),s.p=e})(),(()=>{var e={792:0};s.f.j=(t,n)=>{var r=s.o(e,t)?e[t]:void 0;if(0!==r)if(r)n.push(r[2]);else{var i=new Promise(((n,i)=>r=e[t]=[n,i]));n.push(r[2]=i);var o=s.p+s.u(t),a=new Error;s.l(o,(n=>{if(s.o(e,t)&&(0!==(r=e[t])&&(e[t]=void 0),r)){var i=n&&("load"===n.type?"missing":n.type),o=n&&n.target&&n.target.src;a.message="Loading chunk "+t+" failed.\n("+i+": "+o+")",a.name="ChunkLoadError",a.type=i,a.request=o,r[1](a)}}),"chunk-"+t,t)}};var t=(t,n)=>{var r,i,[o,a,l]=n,c=0;if(o.some((t=>0!==e[t]))){for(r in a)s.o(a,r)&&(s.m[r]=a[r]);l&&l(s)}for(t&&t(n);c(s=new URL(s+".js",c).href,i[s]||new Promise((i=>{if("document"in self){const e=document.createElement("script");e.src=s,e.onload=i,document.head.appendChild(e)}else e=s,importScripts(s),i()})).then((()=>{let e=i[s];if(!e)throw new Error(`Module ${s} didn’t register its module`);return e})));self.define=(c,d)=>{const n=e||("document"in self?document.currentScript.src:"")||location.href;if(i[n])return;let r={};const l=e=>s(e,n),o={module:{uri:n},exports:r,require:l};i[n]=Promise.all(c.map((e=>o[e]||l(e)))).then((e=>(d(...e),r)))}}define(["./workbox-9a84fccb"],(function(e){"use strict";self.addEventListener("message",(e=>{e.data&&"SKIP_WAITING"===e.data.type&&self.skipWaiting()})),e.precacheAndRoute([{url:"../build/src/client/app.d.ts",revision:"4cfbbd93266f8dc9bc5323c0a7e50d38"},{url:"../build/src/client/components/Analytics/analytics-page.d.ts",revision:"9be2aa3b695539303c647ca843b58c9b"},{url:"../build/src/client/components/Dashboard/Dashboard.d.ts",revision:"e76b58891a0d7cc670960173fba6938d"},{url:"../build/src/client/components/Dashboard/components/Chart.d.ts",revision:"f14f739e77b54b50bfe1ca0afae6519b"},{url:"../build/src/client/components/Dashboard/components/Summary.d.ts",revision:"356239a7dd3418def7583cb0dd211a93"},{url:"../build/src/client/components/Dashboard/fakeData.d.ts",revision:"d5b002984f95c47852298b5114e72553"},{url:"../build/src/client/components/Docs/doc-page.d.ts",revision:"b487101975207d9b54f2d1742702271c"},{url:"../build/src/client/components/FlakeRiskSign/FlakeRiskArrow.d.ts",revision:"4bb67b8aefedd8e0a5484bae85af72ae"},{url:"../build/src/client/components/FlakeRiskSign/FlakeRiskContainer.d.ts",revision:"6a48ae22165482f4330955633c7bc9fc"},{url:"../build/src/client/components/FlakeRiskSign/FlakeRiskSign.d.ts",revision:"43a92543707fcfedb269ce4033cabbf7"},{url:"../build/src/client/components/FlakeRiskSign/FlakeRiskSlice.d.ts",revision:"7b29a95c23ebd554c0c773c45d3ed431"},{url:"../build/src/client/components/LandingPage/carousel.d.ts",revision:"f95a96dccc3f66bd165db0eb46784dc2"},{url:"../build/src/client/components/LandingPage/get-started.d.ts",revision:"bf263eafe13af0310bf54d84b7da211a"},{url:"../build/src/client/components/LandingPage/landing-page.d.ts",revision:"459a3caf09b82b38dd3fdbd93875fc19"},{url:"../build/src/client/components/Login/LoginButton.d.ts",revision:"9047db579384095737c69570ab8bab80"},{url:"../build/src/client/components/Login/login-out.d.ts",revision:"0816174f08525ac98fda0f621567103e"},{url:"../build/src/client/components/nav-bar.d.ts",revision:"31fa00b2c27e25654b15e494f4fda03e"},{url:"../build/src/client/redux/fgSlice.d.ts",revision:"72f65cd5e6e5e99806227748580a1237"},{url:"../build/src/client/redux/hooks.d.ts",revision:"3fa696ea51c9a17647b1295dd9bfa72f"},{url:"../build/src/client/redux/store.d.ts",revision:"92743f8e2418616a4307bb22bb488345"},{url:"../build/src/client/redux/userSlice.d.ts",revision:"55fadb545870e56a0dc5b6aaebdbb993"},{url:"../build/src/client/services/index.d.ts",revision:"cf8c646698123d5e79ade0db8156fce9"},{url:"../build/src/client/supabaseClient.d.ts",revision:"b44afd16089c8cdf953b9cdf7fcd45f4"},{url:"../build/src/index.d.ts",revision:"e2ebd7ddedcadeeadbf819c35985c768"},{url:"../build/src/server/controllers/npmController.d.ts",revision:"58e101551a9654430efb0b079f10b71e"},{url:"../build/src/server/routes/npm.d.ts",revision:"771bf7d34674d13098f348597785e7b0"},{url:"../build/src/server/server.d.ts",revision:"e2ebd7ddedcadeeadbf819c35985c768"},{url:"05c3c6e2e4e64053233a.png",revision:null},{url:"591.js",revision:"afc2c27eacced6e46f7991daa5b2ec9a"},{url:"5e8591c242396582a3c3.png",revision:null},{url:"76183d22af78ff37820d.png",revision:null},{url:"9ba0183330fc2d77497a.png",revision:null},{url:"index.html",revision:"0279605275b59cd1ea23f761d7be8f77"},{url:"main.css",revision:"f4a0c4132798ab86615f8bf4d5c5d36e"},{url:"main.js",revision:"d2299c21fd3d2e17b01f036b935097e1"},{url:"main.js.LICENSE.txt",revision:"8d9ad1b7bec2fe3d532ca1dc9a0eecde"}],{})}));
diff --git a/.editorconfig b/flake-guard-app/.editorconfig
similarity index 100%
rename from .editorconfig
rename to flake-guard-app/.editorconfig
diff --git a/.eslintignore b/flake-guard-app/.eslintignore
similarity index 100%
rename from .eslintignore
rename to flake-guard-app/.eslintignore
diff --git a/flake-guard-app/.eslintrc.json b/flake-guard-app/.eslintrc.json
new file mode 100644
index 0000000..37c8455
--- /dev/null
+++ b/flake-guard-app/.eslintrc.json
@@ -0,0 +1,32 @@
+{
+ "extends": "./node_modules/gts/",
+ "rules": {
+ "node/no-unpublished-require": [
+ "error",
+ {
+ "allowModules": [
+ "html-webpack-plugin",
+ "mini-css-extract-plugin",
+ "workbox-webpack-plugin"
+ ]
+ }
+ ]
+ },
+ "plugins": ["react", "@typescript-eslint", "prettier", "jest"],
+ "settings": {
+ "react": {
+ "version": "detect"
+ }
+ },
+ "overrides": [
+ // Different options based on file extensions.
+ {
+ "parser": "babel-eslint",
+ "files": ["**.js"]
+ },
+ {
+ "parser": "@typescript-eslint/parser",
+ "files": ["**.ts"]
+ }
+ ]
+}
diff --git a/flake-guard-app/.gitignore b/flake-guard-app/.gitignore
new file mode 100644
index 0000000..a682e36
--- /dev/null
+++ b/flake-guard-app/.gitignore
@@ -0,0 +1,7 @@
+node_modules
+.env
+.DS_Store
+build/
+dist/
+coverage/
+package-lock.json
\ No newline at end of file
diff --git a/.prettierrc.js b/flake-guard-app/.prettierrc.js
similarity index 100%
rename from .prettierrc.js
rename to flake-guard-app/.prettierrc.js
diff --git a/flake-guard-app/Dockerfile b/flake-guard-app/Dockerfile
new file mode 100644
index 0000000..92ae4d5
--- /dev/null
+++ b/flake-guard-app/Dockerfile
@@ -0,0 +1,12 @@
+FROM node:20.12
+
+WORKDIR /usr/src/app
+
+COPY . /usr/src/app/
+
+RUN npm install
+RUN npm run build
+
+EXPOSE 3000
+
+ENTRYPOINT ["node", "./src/server/buildServer.js"]
diff --git a/README.md b/flake-guard-app/README.md
similarity index 100%
rename from README.md
rename to flake-guard-app/README.md
diff --git a/flake-guard-app/babel.config.js b/flake-guard-app/babel.config.js
new file mode 100644
index 0000000..f455eca
--- /dev/null
+++ b/flake-guard-app/babel.config.js
@@ -0,0 +1,8 @@
+module.exports = {
+ presets: [
+ ['@babel/preset-env', {targets: {node: 'current'}}],
+ '@babel/preset-typescript',
+ // ['@babel/preset-react', {runtime: 'automatic'}],
+ ],
+ };
+
\ No newline at end of file
diff --git a/dataobj.json b/flake-guard-app/dataobj.json
similarity index 100%
rename from dataobj.json
rename to flake-guard-app/dataobj.json
diff --git a/global.d.ts b/flake-guard-app/global.d.ts
similarity index 100%
rename from global.d.ts
rename to flake-guard-app/global.d.ts
diff --git a/flake-guard-app/jest.config.js b/flake-guard-app/jest.config.js
new file mode 100644
index 0000000..ce660a7
--- /dev/null
+++ b/flake-guard-app/jest.config.js
@@ -0,0 +1,201 @@
+/**
+ * For a detailed explanation regarding each configuration property, visit:
+ * https://jestjs.io/docs/configuration
+ */
+
+/** @type {import('jest').Config} */
+const config = {
+ // All imported modules in your tests should be mocked automatically
+ // automock: false,
+
+ // Stop running tests after `n` failures
+ // bail: 0,
+
+ // The directory where Jest should store its cached dependency information
+ // cacheDirectory: "/private/var/folders/52/mww99m6x1qs17jmf0g9d5p040000gn/T/jest_dx",
+
+ // Automatically clear mock calls, instances, contexts and results before every test
+ clearMocks: true,
+
+ // Indicates whether the coverage information should be collected while executing the test
+ collectCoverage: true,
+
+ // An array of glob patterns indicating a set of files for which coverage information should be collected
+ // collectCoverageFrom: undefined,
+
+ // The directory where Jest should output its coverage files
+ coverageDirectory: 'coverage',
+
+ // An array of regexp pattern strings used to skip coverage collection
+ // coveragePathIgnorePatterns: [
+ // "/node_modules/"
+ // ],
+
+ // Indicates which provider should be used to instrument code for coverage
+ coverageProvider: 'v8',
+
+ // A list of reporter names that Jest uses when writing coverage reports
+ // coverageReporters: [
+ // "json",
+ // "text",
+ // "lcov",
+ // "clover"
+ // ],
+
+ // An object that configures minimum threshold enforcement for coverage results
+ // coverageThreshold: undefined,
+
+ // A path to a custom dependency extractor
+ // dependencyExtractor: undefined,
+
+ // Make calling deprecated APIs throw helpful error messages
+ // errorOnDeprecated: false,
+
+ // The default configuration for fake timers
+ // fakeTimers: {
+ // "enableGlobally": false
+ // },
+
+ // Force coverage collection from ignored files using an array of glob patterns
+ // forceCoverageMatch: [],
+
+ // A path to a module which exports an async function that is triggered once before all test suites
+ // globalSetup: undefined,
+
+ // A path to a module which exports an async function that is triggered once after all test suites
+ // globalTeardown: undefined,
+
+ // A set of global variables that need to be available in all test environments
+ // globals: {},
+
+ // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
+ // maxWorkers: "50%",
+
+ // An array of directory names to be searched recursively up from the requiring module's location
+ // moduleDirectories: [
+ // "node_modules"
+ // ],
+
+ // An array of file extensions your modules use
+ // moduleFileExtensions: [
+ // "js",
+ // "mjs",
+ // "cjs",
+ // "jsx",
+ // "ts",
+ // "tsx",
+ // "json",
+ // "node"
+ // ],
+
+ // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
+ // moduleNameMapper: {},
+
+ // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
+ // modulePathIgnorePatterns: [],
+
+ // Activates notifications for test results
+ // notify: false,
+
+ // An enum that specifies notification mode. Requires { notify: true }
+ // notifyMode: "failure-change",
+
+ // A preset that is used as a base for Jest's configuration
+ // preset: 'ts-jest',
+
+ // Run tests from one or more projects
+ // projects: undefined,
+
+ // Use this configuration option to add custom reporters to Jest
+ // reporters: undefined,
+
+ // Automatically reset mock state before every test
+ // resetMocks: false,
+
+ // Reset the module registry before running each individual test
+ // resetModules: false,
+
+ // A path to a custom resolver
+ // resolver: undefined,
+
+ // Automatically restore mock state and implementation before every test
+ // restoreMocks: false,
+
+ // The root directory that Jest should scan for tests and modules within
+ // rootDir: undefined,
+
+ // A list of paths to directories that Jest should use to search for files in
+ // roots: [
+ // ""
+ // ],
+
+ // Allows you to use a custom runner instead of Jest's default test runner
+ // runner: "jest-runner",
+
+ // The paths to modules that run some code to configure or set up the testing environment before each test
+ // setupFiles: [],
+
+ // A list of paths to modules that run some code to configure or set up the testing framework before each test
+ // setupFilesAfterEnv: [],
+
+ // The number of seconds after which a test is considered as slow and reported as such in the results.
+ // slowTestThreshold: 5,
+
+ // A list of paths to snapshot serializer modules Jest should use for snapshot testing
+ // snapshotSerializers: [],
+
+ // The test environment that will be used for testing
+ testEnvironment: 'jest-environment-node',
+
+ // Options that will be passed to the testEnvironment
+ // testEnvironmentOptions: {},
+
+ // Adds a location field to test results
+ // testLocationInResults: false,
+
+ // The glob patterns Jest uses to detect test files
+ // testMatch: [
+ // "**/__tests__/**/*.[jt]s?(x)",
+ // "**/?(*.)+(spec|test).[tj]s?(x)"
+ // ],
+
+ // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
+ // testPathIgnorePatterns: [
+ // "/node_modules/"
+ // ],
+
+ // The regexp pattern or array of patterns that Jest uses to detect test files
+ // testRegex: [],
+
+ // This option allows the use of a custom results processor
+ // testResultsProcessor: undefined,
+
+ // This option allows use of a custom test runner
+ // testRunner: "jest-circus/runner",
+
+ // A map from regular expressions to paths to transformers
+ // transform: undefined,
+ transform: {
+ "^.+\\.(t|j)sx?$": "ts-jest",
+ ".+\\.(css|scss|png|jpg|svg)$": "jest-transform-stub"
+ },
+ // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
+ // transformIgnorePatterns: [
+ // "/node_modules/",
+ // "\\.pnp\\.[^\\/]+$"
+ // ],
+
+ // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
+ // unmockedModulePathPatterns: undefined,
+
+ // Indicates whether each individual test should be reported during the run
+ // verbose: undefined,
+
+ // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
+ // watchPathIgnorePatterns: [],
+
+ // Whether to use watchman for file crawling
+ // watchman: true,
+};
+
+module.exports = config;
diff --git a/flake-guard-app/jest.setup.ts b/flake-guard-app/jest.setup.ts
new file mode 100644
index 0000000..d7e86cb
--- /dev/null
+++ b/flake-guard-app/jest.setup.ts
@@ -0,0 +1 @@
+import 'ts-node/register';
diff --git a/package.json b/flake-guard-app/package.json
similarity index 56%
rename from package.json
rename to flake-guard-app/package.json
index be46dc2..56ac272 100644
--- a/package.json
+++ b/flake-guard-app/package.json
@@ -5,7 +5,7 @@
"main": "./src/index.tsx",
"scripts": {
"start": "concurrently \"webpack serve --open\" \"nodemon ./src/server/server.ts\"",
- "test": "echo \"Error: no test specified\" && exit 1",
+ "test": "jest",
"build": "webpack --mode=production --node-env=production",
"build:dev": "webpack --mode=development",
"build:prod": "webpack --mode=production --node-env=production",
@@ -14,34 +14,45 @@
"lint": "gts lint",
"clean": "gts clean",
"compile": "tsc",
- "fix": "gts fix",
- "prepare": "npm run compile",
- "pretest": "npm run compile",
- "posttest": "npm run lint"
+ "fix": "gts fix"
},
"author": "",
"license": "ISC",
"devDependencies": {
+ "@babel/core": "^7.24.8",
+ "@babel/preset-env": "^7.24.8",
+ "@babel/preset-react": "^7.24.7",
+ "@babel/preset-typescript": "^7.24.7",
"@eslint/js": "^9.3.0",
+ "@jest/globals": "^29.7.0",
+ "@testing-library/dom": "^10.3.2",
+ "@testing-library/jest-dom": "^6.4.6",
+ "@testing-library/react": "^16.0.0",
"@types/bootstrap": "^5.2.10",
"@types/cors": "^2.8.17",
"@types/express": "^4.17.21",
"@types/faker": "^6.6.9",
+ "@types/jest": "^29.5.12",
+ "@types/lodash": "^4.17.6",
"@types/node": "^20.14.2",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@types/react-redux": "^7.1.33",
+ "@types/react-router-dom": "^5.3.3",
"@types/webpack": "^5.28.5",
+ "babel-jest": "^29.7.0",
"css-loader": "^7.1.2",
"eslint": "^8.57.0",
- "eslint-plugin-react": "^7.34.2",
- "flake-guard": "^2.2.0",
+ "eslint-plugin-react": "^7.34.4",
"globals": "^15.3.0",
"gts": "^5.3.0",
"html-webpack-plugin": "^5.6.0",
+ "jest": "^29.7.0",
+ "jest-environment-jsdom": "^29.7.0",
"mini-css-extract-plugin": "^2.9.0",
"style-loader": "^4.0.0",
"tailwindcss": "^3.4.4",
+ "ts-jest": "^29.2.2",
"ts-loader": "^9.5.1",
"ts-node": "^10.9.2",
"tslint-config-prettier": "^1.18.0",
@@ -53,8 +64,25 @@
"workbox-webpack-plugin": "^7.1.0"
},
"dependencies": {
+ "@coreui/icons": "^3.0.1",
+ "@coreui/react": "^5.1.0",
+ "@emotion/css": "^11.11.2",
+ "@emotion/react": "^11.11.4",
+ "@emotion/styled": "^11.11.5",
+ "@fontsource/roboto": "^5.0.13",
+ "@mui/icons-material": "^5.16.4",
+ "@mui/material": "^5.16.4",
+ "@mui/styled-engine-sc": "^6.0.0-alpha.18",
+ "@mui/system": "^5.15.20",
+ "@mui/x-charts": "^7.7.0",
+ "@nivo/bar": "^0.87.0",
+ "@nivo/calendar": "^0.87.0",
+ "@nivo/core": "^0.87.0",
+ "@nivo/line": "^0.87.0",
+ "@nivo/pie": "^0.87.0",
"@reduxjs/toolkit": "^2.2.5",
"@supabase/supabase-js": "^2.43.4",
+ "@testing-library/user-event": "^14.5.2",
"@types/chart.js": "^2.9.41",
"axios": "^1.7.2",
"body-parser": "^1.20.2",
@@ -66,13 +94,20 @@
"dotenv": "^16.4.5",
"express": "^4.19.2",
"faker": "^5.5.3",
+ "jest-transform-css": "^6.0.1",
+ "jest-transform-stub": "^2.0.0",
"nodemon": "^3.1.2",
"postgres": "^3.4.4",
- "react": "^18.3.1",
- "react-bootstrap": "^2.10.2",
+ "react": "^17.0.0 || ^18.0.0",
+ "react-bootstrap": "^2.10.4",
"react-chartjs-2": "^5.2.0",
- "react-dom": "^18.3.1",
+ "react-dom": "^17.0.0 || ^18.0.0",
+ "react-icons": "^5.2.1",
+ "react-icons-kit": "^2.0.0",
+ "react-pro-sidebar": "^1.1.0",
"react-redux": "^9.1.2",
- "react-router-dom": "^6.23.1"
+ "react-router-dom": "^6.25.0",
+ "recharts": "^2.12.7",
+ "styled-components": "^6.1.11"
}
}
diff --git a/flake-guard-app/src/client/app.test.tsx b/flake-guard-app/src/client/app.test.tsx
new file mode 100644
index 0000000..1fefb70
--- /dev/null
+++ b/flake-guard-app/src/client/app.test.tsx
@@ -0,0 +1,16 @@
+
+/** @jest-environment jsdom */
+//https://testing-library.com/docs/example-react-router/
+import * as React from 'react';
+import { render } from '@testing-library/react';
+import App from './app';
+
+describe('App', () => {
+ it('renders App component', () => {
+ render();
+
+
+ });
+});
+
+//NEED TO MANUALLY SET THE ENVIRONMENT BECAUSE OUR CONFIG ENVIRONMENT IS SET WITH NODE ENVIRONMENT FOR BACKEND TESTING
\ No newline at end of file
diff --git a/flake-guard-app/src/client/app.tsx b/flake-guard-app/src/client/app.tsx
new file mode 100644
index 0000000..2d1f5d7
--- /dev/null
+++ b/flake-guard-app/src/client/app.tsx
@@ -0,0 +1,46 @@
+import React from 'react';
+// import 'bootstrap/dist/css/bootstrap.min.css';
+// import 'bootstrap/dist/js/bootstrap.bundle.min.js';
+import Landing from './components/LandingPage/landing-page';
+import DocPage from './components/Docs/DocPage';
+import UserDashboard from './newDashboard/dashboard/NewUserDashboard';
+import TempDashboard from './components/Dashboard/TempDashboard';
+import DecisionPage from './components/Dashboard/DecisionPage';
+import NewUserDashboard from './newDashboard/dashboard/NewUserDashboard';
+import Calendar from './newDashboard/components/calendar/index';
+import PieChart from './newDashboard/components/pie/index';
+import LineChart from './newDashboard/components/line/index';
+
+import {
+ BrowserRouter as Router,
+ Routes,
+ Route,
+ Navigate,
+} from 'react-router-dom';
+
+const App: React.FC = () => {
+ // Define the style object
+
+ return (
+
+ {/* Alias of BrowserRouter i.e. Router */}
+
+
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ {/* Redirect any unmatched routes to home */}
+ } />
+
+
+
+ );
+};
+
+export default App;
diff --git a/flake-guard-app/src/client/assets/advantage1.png b/flake-guard-app/src/client/assets/advantage1.png
new file mode 100644
index 0000000..ec018b4
Binary files /dev/null and b/flake-guard-app/src/client/assets/advantage1.png differ
diff --git a/flake-guard-app/src/client/assets/advantage2.png b/flake-guard-app/src/client/assets/advantage2.png
new file mode 100644
index 0000000..720b1e4
Binary files /dev/null and b/flake-guard-app/src/client/assets/advantage2.png differ
diff --git a/flake-guard-app/src/client/assets/advantage3.png b/flake-guard-app/src/client/assets/advantage3.png
new file mode 100644
index 0000000..c617950
Binary files /dev/null and b/flake-guard-app/src/client/assets/advantage3.png differ
diff --git a/flake-guard-app/src/client/assets/advantage4.png b/flake-guard-app/src/client/assets/advantage4.png
new file mode 100644
index 0000000..c4445d7
Binary files /dev/null and b/flake-guard-app/src/client/assets/advantage4.png differ
diff --git a/flake-guard-app/src/client/assets/arrow.png b/flake-guard-app/src/client/assets/arrow.png
new file mode 100644
index 0000000..c3760a5
Binary files /dev/null and b/flake-guard-app/src/client/assets/arrow.png differ
diff --git a/src/client/assets/bg_1.png b/flake-guard-app/src/client/assets/bg_1.png
similarity index 100%
rename from src/client/assets/bg_1.png
rename to flake-guard-app/src/client/assets/bg_1.png
diff --git a/src/client/assets/bg_2.png b/flake-guard-app/src/client/assets/bg_2.png
similarity index 100%
rename from src/client/assets/bg_2.png
rename to flake-guard-app/src/client/assets/bg_2.png
diff --git a/src/client/assets/bg_login.png b/flake-guard-app/src/client/assets/bg_login.png
similarity index 100%
rename from src/client/assets/bg_login.png
rename to flake-guard-app/src/client/assets/bg_login.png
diff --git a/src/client/assets/code_2.png b/flake-guard-app/src/client/assets/code_2.png
similarity index 100%
rename from src/client/assets/code_2.png
rename to flake-guard-app/src/client/assets/code_2.png
diff --git a/flake-guard-app/src/client/assets/constellation.png b/flake-guard-app/src/client/assets/constellation.png
new file mode 100644
index 0000000..8d6380b
Binary files /dev/null and b/flake-guard-app/src/client/assets/constellation.png differ
diff --git a/flake-guard-app/src/client/assets/download.png b/flake-guard-app/src/client/assets/download.png
new file mode 100644
index 0000000..3c659ad
Binary files /dev/null and b/flake-guard-app/src/client/assets/download.png differ
diff --git a/src/client/assets/earth.png b/flake-guard-app/src/client/assets/earth.png
similarity index 100%
rename from src/client/assets/earth.png
rename to flake-guard-app/src/client/assets/earth.png
diff --git a/flake-guard-app/src/client/assets/github-mark-white.png b/flake-guard-app/src/client/assets/github-mark-white.png
new file mode 100644
index 0000000..50b8175
Binary files /dev/null and b/flake-guard-app/src/client/assets/github-mark-white.png differ
diff --git a/flake-guard-app/src/client/assets/github-mark.png b/flake-guard-app/src/client/assets/github-mark.png
new file mode 100644
index 0000000..6cb3b70
Binary files /dev/null and b/flake-guard-app/src/client/assets/github-mark.png differ
diff --git a/flake-guard-app/src/client/assets/github.png b/flake-guard-app/src/client/assets/github.png
new file mode 100644
index 0000000..6cb3b70
Binary files /dev/null and b/flake-guard-app/src/client/assets/github.png differ
diff --git a/src/client/assets/lightbulb.png b/flake-guard-app/src/client/assets/lightbulb.png
similarity index 100%
rename from src/client/assets/lightbulb.png
rename to flake-guard-app/src/client/assets/lightbulb.png
diff --git a/flake-guard-app/src/client/assets/logo.png b/flake-guard-app/src/client/assets/logo.png
new file mode 100644
index 0000000..99ba824
Binary files /dev/null and b/flake-guard-app/src/client/assets/logo.png differ
diff --git a/src/client/assets/logo_1.png b/flake-guard-app/src/client/assets/logo_1.png
similarity index 100%
rename from src/client/assets/logo_1.png
rename to flake-guard-app/src/client/assets/logo_1.png
diff --git a/src/client/assets/logo_2.png b/flake-guard-app/src/client/assets/logo_2.png
similarity index 100%
rename from src/client/assets/logo_2.png
rename to flake-guard-app/src/client/assets/logo_2.png
diff --git a/src/client/assets/logo_3.png b/flake-guard-app/src/client/assets/logo_3.png
similarity index 100%
rename from src/client/assets/logo_3.png
rename to flake-guard-app/src/client/assets/logo_3.png
diff --git a/src/client/assets/logo_4.png b/flake-guard-app/src/client/assets/logo_4.png
similarity index 100%
rename from src/client/assets/logo_4.png
rename to flake-guard-app/src/client/assets/logo_4.png
diff --git a/flake-guard-app/src/client/assets/nasa.png b/flake-guard-app/src/client/assets/nasa.png
new file mode 100644
index 0000000..796b58b
Binary files /dev/null and b/flake-guard-app/src/client/assets/nasa.png differ
diff --git a/src/client/assets/npm-logo.png b/flake-guard-app/src/client/assets/npm-logo.png
similarity index 100%
rename from src/client/assets/npm-logo.png
rename to flake-guard-app/src/client/assets/npm-logo.png
diff --git a/flake-guard-app/src/client/assets/npm.png b/flake-guard-app/src/client/assets/npm.png
new file mode 100644
index 0000000..37e12d7
Binary files /dev/null and b/flake-guard-app/src/client/assets/npm.png differ
diff --git a/flake-guard-app/src/client/assets/signout.png b/flake-guard-app/src/client/assets/signout.png
new file mode 100644
index 0000000..849455a
Binary files /dev/null and b/flake-guard-app/src/client/assets/signout.png differ
diff --git a/flake-guard-app/src/client/components/Analytics/analytics.test.tsx b/flake-guard-app/src/client/components/Analytics/analytics.test.tsx
new file mode 100644
index 0000000..dd3843b
--- /dev/null
+++ b/flake-guard-app/src/client/components/Analytics/analytics.test.tsx
@@ -0,0 +1 @@
+/** @jest-environment jsdom */
diff --git a/flake-guard-app/src/client/components/Analytics/assertion-failures-percent.tsx b/flake-guard-app/src/client/components/Analytics/assertion-failures-percent.tsx
new file mode 100644
index 0000000..64bb8f2
--- /dev/null
+++ b/flake-guard-app/src/client/components/Analytics/assertion-failures-percent.tsx
@@ -0,0 +1,21 @@
+import {TestResult} from '../../types';
+
+export const assertionFailedPercent = (
+ results: TestResult[]
+): string[] | undefined => {
+ if (!results || results.length === 0) return undefined;
+
+ const failingAssertions = results
+ .filter(result => result.failed > 0 && result.passed === 0)
+ .map(result => result.name);
+
+ return failingAssertions.length > 0 ? failingAssertions : undefined;
+};
+// src/client/Analytics/assertion-failures-percent.ts
+// import {TestResult} from '../../types';
+
+// export const assertionFailedPercent = (results: TestResult[]): string[] => {
+// return results
+// .filter(result => result.passed === 0 && result.failed > 0)
+// .map(result => result.name);
+// };
diff --git a/flake-guard-app/src/client/components/Analytics/flake-percentage.tsx b/flake-guard-app/src/client/components/Analytics/flake-percentage.tsx
new file mode 100644
index 0000000..025fafa
--- /dev/null
+++ b/flake-guard-app/src/client/components/Analytics/flake-percentage.tsx
@@ -0,0 +1,50 @@
+// flakePercentageUtils.ts
+
+interface AssertionResult {
+ passed: boolean;
+ failed: boolean;
+}
+
+// export const calculateFlakePercentage = (
+// results: TestResult[]
+// ): number | undefined => {
+// let totalPassed = 0;
+// let totalFailed = 0;
+// let totalSkipped = 0;
+
+// for (const result of results) {
+// totalPassed += result.passed;
+// totalFailed += result.failed;
+// totalSkipped += result.skipped;
+// }
+
+// const totalTests = totalPassed + totalFailed + totalSkipped;
+// if (totalTests === 0) {
+// return undefined; // Return undefined if no tests were executed
+// }
+
+// const flakePercentage = (totalFailed / totalTests) * 100;
+// return flakePercentage;
+// };
+export const calculateFlakePercentage = (
+ assertionResults: AssertionResult[]
+): number | undefined => {
+ let flakeyCount = 0;
+ let totalAssertions = 0;
+
+ for (const assertion of assertionResults) {
+ totalAssertions++;
+
+ // Check if the assertion has at least one pass and one fail
+ if (assertion.passed && assertion.failed) {
+ flakeyCount++;
+ }
+ }
+
+ if (totalAssertions === 0) {
+ return undefined; // No assertions were executed
+ }
+
+ const flakePercentage = (flakeyCount / totalAssertions) * 100;
+ return flakePercentage;
+};
diff --git a/flake-guard-app/src/client/components/Analytics/overall-failed-percentage.tsx b/flake-guard-app/src/client/components/Analytics/overall-failed-percentage.tsx
new file mode 100644
index 0000000..4a31416
--- /dev/null
+++ b/flake-guard-app/src/client/components/Analytics/overall-failed-percentage.tsx
@@ -0,0 +1,27 @@
+// TotalfailedPercentageUtils.ts
+
+interface TestResult {
+ passed: number;
+ failed: number;
+ skipped: number;
+}
+
+export const failedPercentage = (results: TestResult[]): number | undefined => {
+ let totalPassed = 0;
+ let totalFailed = 0;
+ let totalSkipped = 0;
+
+ for (const result of results) {
+ totalPassed += result.passed;
+ totalFailed += result.failed;
+ totalSkipped += result.skipped;
+ }
+
+ const totalTests = totalPassed + totalFailed + totalSkipped;
+ if (totalTests === 0) {
+ return undefined; // Return undefined if no tests were executed
+ }
+
+ const failedPercentage = (totalFailed / totalTests) * 100;
+ return failedPercentage;
+};
diff --git a/flake-guard-app/src/client/components/Dashboard/Additional_Dash.tsx b/flake-guard-app/src/client/components/Dashboard/Additional_Dash.tsx
new file mode 100644
index 0000000..7e66838
--- /dev/null
+++ b/flake-guard-app/src/client/components/Dashboard/Additional_Dash.tsx
@@ -0,0 +1,8 @@
+import React from 'react';
+
+const AdditionalDashboard = (): JSX.Element => {
+ console.log('test');
+ return <>Hi>; // or return null; if you don't need to render anything
+};
+
+export default AdditionalDashboard;
diff --git a/flake-guard-app/src/client/components/Dashboard/Dashboard.tsx b/flake-guard-app/src/client/components/Dashboard/Dashboard.tsx
new file mode 100644
index 0000000..41f7ddc
--- /dev/null
+++ b/flake-guard-app/src/client/components/Dashboard/Dashboard.tsx
@@ -0,0 +1,377 @@
+import {useEffect, useState} from 'react';
+import {api} from '../../services/index';
+import '../../../styles/dashboard.css';
+import '../../../styles/riskSign.css';
+import {useParams} from 'react-router-dom';
+import NavBarHeading from '../nav-bar';
+import Footer from '../footer';
+import {calculateFlakePercentage} from '../Analytics/flake-percentage';
+import FlakeRiskContainer from '../FlakeRiskSign/FlakeRiskContainer';
+import {failedPercentage} from '../Analytics/overall-failed-percentage';
+import ExampleLineChart from './charts/chart';
+// import {assertionFailedPercent} from '../Analytics/assertion-failures-percent';
+// import FlakeGauge from './components/FlakeGauge';
+
+const Dashboard = (): JSX.Element => {
+ const [metrics, setMetrics] = useState<{[key: string]: number} | undefined>(
+ undefined
+ );
+ const [fetchResults, setFetchResults] = useState([]);
+ const [flakePercentage, setFlakePercentage] = useState(
+ undefined
+ );
+
+ const {id} = useParams();
+ console.log('id', id);
+
+ useEffect(() => {
+ const fetchMetrics = async () => {
+ try {
+ // remove get request from db - will add conditional later for permanent users
+ // const response = await api.get('/results/');
+ // console.log(response);
+ const response = await api.get(`/tempDash/${id}`);
+ await api.delete(`/tempDash/${id}`);
+ const results = response.data;
+ console.log('retrieved cached results', results);
+ setFetchResults(results);
+
+ const flakePercentage = calculateFlakePercentage(results); // Calculate flake percentage
+ setFlakePercentage(flakePercentage); // Set flakePercentage state
+
+ let totalPassed = 0;
+ let totalFailed = 0;
+ let totalSkipped = 0;
+
+ for (const result of results) {
+ totalPassed += result.passed;
+ totalFailed += result.failed;
+ totalSkipped += result.skipped;
+ }
+
+ const labelsArr = {
+ passed: totalPassed,
+ failed: totalFailed,
+ skipped: totalSkipped,
+ };
+
+ setMetrics(labelsArr); // Set metrics state
+ } catch (error) {
+ console.error('Error fetching data:', error);
+ }
+ };
+
+ fetchMetrics();
+ }, []);
+
+ return (
+ <>
+
+
+
+ {/*
*/}
+ {flakePercentage !== undefined && (
+
+
Assertion Flakiness:
+
{calculateFlakePercentage(fetchResults)}%
+
+ )}
+ {flakePercentage !== undefined && (
+
+
Test Suite Failures:
+
{failedPercentage(fetchResults)}%
+
+ )}
+ {/* {flakePercentage !== undefined && (
+
+
100% Failure:
+
{assertionFailedPercent(fetchResults)}%
+
+ )} */}
+
+
+ {/* */}
+
+
+
+
+
+
+ >
+ );
+};
+
+export default Dashboard;
+// import React, {useEffect, useState} from 'react';
+// import {api} from '../../services/index';
+// import '../../../styles/dashboard.css';
+// import Summary from './components/Summary';
+// import AssertionsGraph from './components/AssertionsGraph';
+// import DisplayErrors from './components/DisplayErrors';
+// import Trends from './components/Trends';
+// import NavBarHeading from '../nav-bar';
+// import Footer from '../footer';
+// import {calculateFlakePercentage} from '../Analytics/flake-percentage';
+// import FlakeRiskContainer from '../FlakeRiskSign/FlakeRiskContainer';
+// import {failedPercentage} from '../Analytics/overall-failed-percentage';
+// import {assertionFailedPercent} from '../Analytics/assertion-failures-percent';
+// import {TestResult, MetricsData} from '../../types';
+
+// const Dashboard = (): JSX.Element => {
+// const [metrics, setMetrics] = useState<{[key: string]: number} | undefined>(
+// undefined
+// );
+// const [fetchResults, setFetchResults] = useState([]);
+// const [flakePercentage, setFlakePercentage] = useState(
+// undefined
+// );
+// const [failingAssertions, setFailingAssertions] = useState<
+// string[] | undefined
+// >(undefined);
+
+// useEffect(() => {
+// const fetchMetrics = async () => {
+// try {
+// const response = await api.get('/results');
+// const results: TestResult[] = response.data;
+// setFetchResults(results);
+
+// const flakePercentage = calculateFlakePercentage(results);
+// setFlakePercentage(flakePercentage);
+
+// let totalPassed = 0;
+// let totalFailed = 0;
+// let totalSkipped = 0;
+
+// for (const result of results) {
+// totalPassed += result.passed;
+// totalFailed += result.failed;
+// totalSkipped += result.skipped;
+// }
+
+// const labelsArr = {
+// passed: totalPassed,
+// failed: totalFailed,
+// skipped: totalSkipped,
+// };
+
+// setMetrics(labelsArr);
+
+// const failingAssertionsList = assertionFailedPercent(results);
+// setFailingAssertions(failingAssertionsList);
+// } catch (error) {
+// console.error('Error fetching data:', error);
+// }
+// };
+
+// fetchMetrics();
+// }, []);
+
+// return (
+// <>
+//
+//
+//
+//
+//
+//
+// {metrics && }
+//
+//
+//
+//
+//
+//
+//
+// {flakePercentage !== undefined && (
+//
+//
Overall Test Flakiness:
+//
{flakePercentage}%
+//
+// )}
+// {flakePercentage !== undefined && (
+//
+//
Overall Test Failures:
+//
{failedPercentage(fetchResults)}%
+//
+// )}
+// {failingAssertions !== undefined && (
+//
+//
100% Failing Assertions:
+// {failingAssertions.length > 0 ? (
+//
+// {failingAssertions.map((assertionName, index) => (
+// - {assertionName}
+// ))}
+//
+// ) : (
+//
No assertions are failing 100% of the time.
+// )}
+//
+// )}
+//
+//
+//
+//
+//
+// >
+// );
+// };
+
+// export default Dashboard;
+
+// import React, {useEffect, useState} from 'react';
+// import {api} from '../../services/index';
+// import '../../../styles/dashboard.css';
+// import Summary from './components/Summary';
+// import AssertionsGraph from './components/AssertionsGraph';
+// import DisplayErrors from './components/DisplayErrors';
+// import Trends from './components/Trends';
+// import NavBarHeading from '../nav-bar';
+// import Footer from '../footer';
+// import {calculateFlakePercentage} from '../Analytics/flake-percentage';
+// import FlakeRiskContainer from '../FlakeRiskSign/FlakeRiskContainer';
+// import {failedPercentage} from '../Analytics/overall-failed-percentage';
+// import {assertionFailedPercent} from '../Analytics/assertion-failures-percent';
+// import {TestResult, MetricsData} from '../../types';
+
+// const Dashboard = (): JSX.Element => {
+// const [metrics, setMetrics] = useState<{[key: string]: number} | undefined>(
+// undefined
+// );
+// const [fetchResults, setFetchResults] = useState([]);
+// const [flakePercentage, setFlakePercentage] = useState(
+// undefined
+// );
+// const [failingAssertions, setFailingAssertions] = useState<
+// string[] | undefined
+// >(undefined);
+
+// useEffect(() => {
+// const fetchMetrics = async () => {
+// try {
+// const response = await api.get('/results');
+// const results: TestResult[] = response.data;
+// setFetchResults(results);
+
+// const flakePercentage = calculateFlakePercentage(results);
+// setFlakePercentage(flakePercentage);
+
+// let totalPassed = 0;
+// let totalFailed = 0;
+// let totalSkipped = 0;
+
+// for (const result of results) {
+// totalPassed += result.passed;
+// totalFailed += result.failed;
+// totalSkipped += result.skipped;
+// }
+
+// const labelsArr = {
+// passed: totalPassed,
+// failed: totalFailed,
+// skipped: totalSkipped,
+// };
+
+// setMetrics(labelsArr);
+
+// const failingAssertionsList = assertionFailedPercent(results);
+// setFailingAssertions(failingAssertionsList);
+// } catch (error) {
+// console.error('Error fetching data:', error);
+// }
+// };
+
+// fetchMetrics();
+// }, []);
+
+// return (
+// <>
+//
+//
+//
+//
+//
+//
+// {metrics && }
+//
+//
+//
+//
+//
+//
+//
+// {flakePercentage !== undefined && (
+//
+//
Overall Test Flakiness:
+//
{flakePercentage}%
+//
+// )}
+// {flakePercentage !== undefined && (
+//
+//
Overall Test Failures:
+//
{failedPercentage(fetchResults)}%
+//
+// )}
+// {failingAssertions !== undefined && (
+//
+//
100% Failing Assertions:
+// {failingAssertions.length > 0 ? (
+//
+// {failingAssertions.map((assertionName, index) => (
+// - {assertionName}
+// ))}
+//
+// ) : (
+//
No assertions are failing 100% of the time.
+// )}
+//
+// )}
+//
+//
+//
+//
+//
+// >
+// );
+// };
+
+// export default Dashboard;
diff --git a/flake-guard-app/src/client/components/Dashboard/DecisionPage.tsx b/flake-guard-app/src/client/components/Dashboard/DecisionPage.tsx
new file mode 100644
index 0000000..d199eb3
--- /dev/null
+++ b/flake-guard-app/src/client/components/Dashboard/DecisionPage.tsx
@@ -0,0 +1,68 @@
+import {useEffect, useState} from 'react';
+import {useNavigate, useParams} from 'react-router-dom';
+import {supabaseClient} from '../../supabaseClient';
+import {api} from '../../services/index';
+import github from '../../assets/github-mark-white.png';
+
+const DecisionPage: React.FC = () => {
+ const [loggedIn, setLoggedIn] = useState(false);
+ const navigate = useNavigate();
+ const {id} = useParams();
+
+ useEffect(() => {
+ const checkIfLoggedIn = async () => {
+ try {
+ const {data, error} = await supabaseClient.auth.getUser();
+
+ if (data && !error) {
+ // get results from cache and save to db
+ const response = await api.get(`/results/${id}`);
+ const results = response.data;
+ await api.post(`/userDash/${data.user.id}`, {results});
+ await api.delete(`/tempDash/${id}`);
+ // route to user dashboard
+ const url: string = `/dashboard/user/${data.user.id}`;
+ navigate(url);
+ }
+ } catch (error) {
+ console.error(
+ 'Error checking user auth or getting results from cache and saving to database: ',
+ error
+ );
+ }
+ };
+ checkIfLoggedIn();
+ }, [loggedIn]);
+
+ const signIn = async () => {
+ try {
+ const signInResponse = await supabaseClient.auth.signInWithOAuth({
+ provider: 'github',
+ options: {redirectTo: `http://localhost:8080/npm/${id}`},
+ });
+ console.log('SIGN IN RESPONSE------>', signInResponse);
+ const {data, error} = await supabaseClient.auth.getUser();
+ if (data.user && !error) setLoggedIn(true);
+ } catch (error) {
+ console.error('Error signing in: ', error);
+ }
+ };
+
+ const goToTemp = (): void => {
+ navigate(`/tempdashboard/${id}`);
+ };
+
+ return (
+
+
+
+
+
+
+ );
+};
+
+export default DecisionPage;
diff --git a/flake-guard-app/src/client/components/Dashboard/TempDashboard.tsx b/flake-guard-app/src/client/components/Dashboard/TempDashboard.tsx
new file mode 100644
index 0000000..666a3b8
--- /dev/null
+++ b/flake-guard-app/src/client/components/Dashboard/TempDashboard.tsx
@@ -0,0 +1,116 @@
+import {useEffect, useState} from 'react';
+import {api} from '../../services/index';
+import '../../../styles/dashboard.css';
+import Summary from './components/Summary';
+import AssertionsGraph from './components/AssertionsGraph';
+import DisplayErrors from './components/DisplayErrors';
+import {useParams} from 'react-router-dom';
+import NavBarHeading from '../nav-bar';
+import Footer from '../footer';
+import {calculateFlakePercentage} from '../Analytics/flake-percentage';
+import FlakeRiskContainer from '../FlakeRiskSign/FlakeRiskContainer';
+import {failedPercentage} from '../Analytics/overall-failed-percentage';
+
+const Dashboard = (): JSX.Element => {
+ const [metrics, setMetrics] = useState<{[key: string]: number} | undefined>(
+ undefined
+ );
+ const [fetchResults, setFetchResults] = useState([]);
+ const [flakePercentage, setFlakePercentage] = useState(
+ undefined
+ );
+
+ const {id} = useParams();
+
+ useEffect(() => {
+ const fetchMetrics = async () => {
+ try {
+ // remove get request from db - will add conditional later for permanent users
+ // const response = await api.get('/results/');
+
+ const response = await api.get(`/tempDash/${id}`);
+ await api.delete(`/tempDash/${id}`);
+ const results = response.data;
+ console.log('retrieved cached results', results);
+ setFetchResults(results);
+
+ const flakePercentage = calculateFlakePercentage(results); // Calculate flake percentage
+ setFlakePercentage(flakePercentage); // Set flakePercentage state
+
+ let totalPassed = 0;
+ let totalFailed = 0;
+ let totalSkipped = 0;
+
+ for (const result of results) {
+ totalPassed += result.passed;
+ totalFailed += result.failed;
+ totalSkipped += result.skipped;
+ }
+
+ const labelsArr = {
+ passed: totalPassed,
+ failed: totalFailed,
+ skipped: totalSkipped,
+ };
+
+ setMetrics(labelsArr); // Set metrics state
+ } catch (error) {
+ console.error('Error fetching data:', error);
+ }
+ };
+
+ fetchMetrics();
+ }, []);
+
+ return (
+ <>
+
+
+
+
+ {flakePercentage !== undefined && (
+
+
Assertion Flakiness:
+
{calculateFlakePercentage(fetchResults)}%
+
+ )}
+ {flakePercentage !== undefined && (
+
+
Test Suite Failures:
+
{failedPercentage(fetchResults)}%
+
+ )}
+ {/* {flakePercentage !== undefined && (
+
+
100% Failure:
+
{assertionFailedPercent(fetchResults)}%
+
+ )} */}
+
+
+ {/* */}
+
+
+
+ >
+ );
+};
+
+export default Dashboard;
diff --git a/flake-guard-app/src/client/components/Dashboard/UserDashboard.tsx b/flake-guard-app/src/client/components/Dashboard/UserDashboard.tsx
new file mode 100644
index 0000000..c325b1b
--- /dev/null
+++ b/flake-guard-app/src/client/components/Dashboard/UserDashboard.tsx
@@ -0,0 +1,36 @@
+import {useEffect, useState} from 'react';
+import {api} from '../../services/index';
+import {useParams} from 'react-router-dom';
+
+const UserDashboard: React.FC = () => {
+ const {userId} = useParams();
+ const [results, setResults] = useState([]);
+
+ useEffect(() => {
+ const getResults = async () => {
+ try {
+ const results = await api.get(`/userDash/${userId}`);
+ const resultsArray = results.data;
+ // add a yyyy-mm-dd date to each result
+ for (const result of resultsArray) {
+ const ts = result.created_at;
+ result.date = ts.slice(0, ts.indexOf('T'));
+ }
+ console.log('RESULTS USERDASH', results, resultsArray)
+ setResults(resultsArray);
+ console.log('ALL SAVED RESULTS FOR USER----->', results.data);
+ } catch (error) {
+ console.log('Error getting results: ', error);
+ }
+ };
+ getResults();
+ }, []);
+
+ return (
+
+
USER DASHBOARD
+
+ );
+};
+
+export default UserDashboard;
diff --git a/flake-guard-app/src/client/components/Dashboard/charts/chart.tsx b/flake-guard-app/src/client/components/Dashboard/charts/chart.tsx
new file mode 100644
index 0000000..38a1777
--- /dev/null
+++ b/flake-guard-app/src/client/components/Dashboard/charts/chart.tsx
@@ -0,0 +1,104 @@
+// import { LineChart, Line, CartesianGrid, XAxis, YAxis, Tooltip } from 'recharts';
+// const data = [{name: 'Test A', uv: 400, pv: 2400, amt: 2400}, ...];
+
+// const renderLineChart = (
+//
+//
+//
+//
+//
+//
+//
+// );
+import React, {PureComponent} from 'react';
+import {
+ LineChart,
+ Line,
+ XAxis,
+ YAxis,
+ CartesianGrid,
+ Tooltip,
+ Legend,
+ ResponsiveContainer,
+} from 'recharts';
+
+const fakeData = [
+ {
+ name: 'Test A',
+ uv: 4000,
+ pv: 2400,
+ amt: 2400,
+ },
+ {
+ name: 'Test B',
+ uv: 3000,
+ pv: 1398,
+ amt: 2210,
+ },
+ {
+ name: 'Test C',
+ uv: 2000,
+ pv: 9800,
+ amt: 2290,
+ },
+ {
+ name: 'Page D',
+ uv: 2780,
+ pv: 3908,
+ amt: 2000,
+ },
+ {
+ name: 'Page E',
+ uv: 1890,
+ pv: 4800,
+ amt: 2181,
+ },
+ {
+ name: 'Page F',
+ uv: 2390,
+ pv: 3800,
+ amt: 2500,
+ },
+ {
+ name: 'Page G',
+ uv: 3490,
+ pv: 4300,
+ amt: 2100,
+ },
+];
+
+export default class ExampleLineChart extends PureComponent {
+ static demoUrl =
+ 'https://codesandbox.io/p/sandbox/line-chart-width-xaxis-padding-8v7952';
+
+ render() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/flake-guard-app/src/client/components/Dashboard/components/AssertionsGraph.tsx b/flake-guard-app/src/client/components/Dashboard/components/AssertionsGraph.tsx
new file mode 100644
index 0000000..9e098f4
--- /dev/null
+++ b/flake-guard-app/src/client/components/Dashboard/components/AssertionsGraph.tsx
@@ -0,0 +1,222 @@
+// import React from 'react';
+// import {
+// Chart as ChartJS,
+// CategoryScale,
+// LinearScale,
+// BarElement,
+// Title,
+// Tooltip,
+// Legend,
+// } from 'chart.js';
+// import {Bar} from 'react-chartjs-2';
+
+// ChartJS.register(
+// CategoryScale,
+// LinearScale,
+// BarElement,
+// Title,
+// Tooltip,
+// Legend
+// );
+
+// interface MetricsData {
+// fullName: string;
+// passed: number;
+// failed: number;
+// skipped: number;
+// }
+
+// interface AssertionsGraphProps {
+// fetchResults: MetricsData[] | undefined;
+// }
+
+// const AssertionsGraph: React.FC = ({fetchResults}) => {
+// const barChartData = {
+// labels: [] as string[],
+// datasets: [
+// {
+// label: 'Passed',
+// data: [] as number[],
+// backgroundColor: 'rgba(81, 209, 156, 0.6)',
+// hoverOffset: 1,
+// barThickness: 15,
+// },
+// {
+// label: 'Failed',
+// data: [] as number[],
+// backgroundColor: 'rgba(255, 49, 49, .9)',
+// hoverOffset: 1,
+// barThickness: 15,
+// },
+// ],
+// };
+
+// if (fetchResults) {
+// for (let i = 0; i < fetchResults.length; i++) {
+// const results = fetchResults[i];
+// console.log('fetchResults', results);
+
+// barChartData.labels.push('assertion ' + (i + 1));
+// barChartData.datasets[0].data.push(results.passed);
+// barChartData.datasets[1].data.push(results.failed);
+// }
+// }
+
+// const options = {
+// indexAxis: 'y' as const,
+// responsive: true,
+// plugins: {
+// legend: {
+// display: true,
+// },
+// title: {
+// display: true,
+// text: 'Assertion Results',
+// font: {
+// size: 18,
+// },
+// },
+// },
+// scales: {
+// x: {
+// stacked: true,
+// },
+// y: {
+// stacked: true,
+// },
+// },
+// };
+
+// return (
+//
+//
+//
+// );
+// };
+
+// export default AssertionsGraph;
+import React from 'react';
+import {
+ Chart as ChartJS,
+ CategoryScale,
+ LinearScale,
+ BarElement,
+ Title,
+ Tooltip,
+ Legend,
+} from 'chart.js';
+import {Bar} from 'react-chartjs-2';
+import Flakiness from '../components/Flakiness';
+
+ChartJS.register(
+ CategoryScale,
+ LinearScale,
+ BarElement,
+ Title,
+ Tooltip,
+ Legend
+);
+
+interface MetricsData {
+ fullName: string;
+ passed: number;
+ failed: number;
+ skipped: number;
+}
+
+interface AssertionsGraphProps {
+ fetchResults: MetricsData[] | undefined;
+}
+
+const AssertionsGraph: React.FC = ({fetchResults}) => {
+ const barChartData = {
+ labels: [] as string[],
+ datasets: [
+ {
+ label: 'Passed',
+ data: [] as number[],
+ backgroundColor: 'rgba(81, 209, 156, 0.6)',
+ hoverOffset: 1,
+ barThickness: 25,
+ },
+ {
+ label: 'Failed',
+ data: [] as number[],
+ backgroundColor: 'rgba(255, 49, 49, .9)',
+ hoverOffset: 1,
+ barThickness: 25,
+ },
+ ],
+ };
+
+ const flaggedIndices = new Set();
+
+ if (fetchResults) {
+ for (let i = 0; i < fetchResults.length; i++) {
+ const results = fetchResults[i];
+ console.log('fetchResults ---> ', results);
+
+ barChartData.labels.push('assertion ' + (i + 1));
+ barChartData.datasets[0].data.push(results.passed);
+ barChartData.datasets[1].data.push(results.failed);
+
+ if (results.passed > 0 && results.failed > 0) {
+ flaggedIndices.add(i);
+ }
+ }
+ }
+
+ const options = {
+ indexAxis: 'y' as const,
+ responsive: true,
+ plugins: {
+ legend: {
+ display: true,
+ },
+ title: {
+ display: true,
+ text: 'Assertion Results',
+ font: {
+ size: 18,
+ },
+ },
+ },
+ scales: {
+ x: {
+ stacked: true,
+ },
+ y: {
+ stacked: true,
+ ticks: {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ callback: function (value: any, index: number, values: any) {
+ if (flaggedIndices.has(index)) {
+ return `* ${barChartData.labels[index]} `;
+ }
+ return barChartData.labels[index];
+ },
+ },
+ },
+ },
+ };
+
+ return (
+
+
+
Flaky Rate (%)
+
+ {fetchResults &&
+ fetchResults.map(result => (
+
+
+
+ ))}
+
+
+
+
+
+ );
+};
+
+export default AssertionsGraph;
diff --git a/flake-guard-app/src/client/components/Dashboard/components/DisplayErrors.tsx b/flake-guard-app/src/client/components/Dashboard/components/DisplayErrors.tsx
new file mode 100644
index 0000000..5ec7ddc
--- /dev/null
+++ b/flake-guard-app/src/client/components/Dashboard/components/DisplayErrors.tsx
@@ -0,0 +1,77 @@
+import React, {useState} from 'react';
+import arrow from '../../../assets/arrow.png';
+
+interface MetricsData {
+ fullName: string;
+ passed: number;
+ failed: number;
+ skipped: number;
+}
+
+interface AssertionsGraphProps {
+ fetchResults: MetricsData[] | undefined;
+}
+
+const DisplayErrors: React.FC = ({fetchResults}) => {
+ const [showDetails, setShowDetails] = useState([]);
+ const [arrowDirections, setArrowDirections] = useState([]);
+
+ const handleToggleDetails = (index: number) => {
+ const updatedShowDetails = [...showDetails];
+ updatedShowDetails[index] = !updatedShowDetails[index];
+ setShowDetails(updatedShowDetails);
+
+ const updatedArrowDirections = [...arrowDirections];
+ updatedArrowDirections[index] = !updatedArrowDirections[index];
+ setArrowDirections(updatedArrowDirections);
+ };
+
+ return (
+
+
Test Results
+
+
+
ERROR
+ {fetchResults ? (
+
+ {fetchResults.map((result, index) => (
+
+
+
+
+ {showDetails[index] && (
+
+
Passed: {result.passed}
+
Failed: {result.failed}
+
Skipped: {result.skipped}
+
+ )}
+
+
+ ))}
+
+ ) : (
+
No data available
+ )}
+
+
+ );
+};
+
+export default DisplayErrors;
diff --git a/flake-guard-app/src/client/components/Dashboard/components/FlakeGauge.tsx b/flake-guard-app/src/client/components/Dashboard/components/FlakeGauge.tsx
new file mode 100644
index 0000000..3431118
--- /dev/null
+++ b/flake-guard-app/src/client/components/Dashboard/components/FlakeGauge.tsx
@@ -0,0 +1,21 @@
+// import React from 'react';
+// import {Gauge, gaugeClasses} from '@mui/x-charts/Gauge';
+
+// const FlakeGauge = (): JSX.Element => {
+// return (
+// `${value} / ${valueMax}`}
+// />
+// );
+// };
+
+// export default FlakeGauge;
diff --git a/flake-guard-app/src/client/components/Dashboard/components/Flakiness.tsx b/flake-guard-app/src/client/components/Dashboard/components/Flakiness.tsx
new file mode 100644
index 0000000..cfc1d74
--- /dev/null
+++ b/flake-guard-app/src/client/components/Dashboard/components/Flakiness.tsx
@@ -0,0 +1,43 @@
+import React from 'react';
+import {
+ Chart as ChartJS,
+ CategoryScale,
+ LinearScale,
+ BarElement,
+ Title,
+ Tooltip,
+ Legend,
+} from 'chart.js';
+import {Bar} from 'react-chartjs-2';
+import {ProgressBar} from 'react-bootstrap';
+// import 'bootstrap/dist/css/bootstrap.min.css';
+
+ChartJS.register(
+ CategoryScale,
+ LinearScale,
+ BarElement,
+ Title,
+ Tooltip,
+ Legend
+);
+
+
+interface AssertionsGraphProps {
+ fetchResults: number;
+}
+
+const Flakiness: React.FC = ({fetchResults}) => {
+ console.log('fetchResults ---> ', fetchResults);
+ return (
+
+
{`${fetchResults}%`}}
+ />
+
+ );
+};
+
+export default Flakiness;
diff --git a/src/client/components/Dashboard/components/Summary.tsx b/flake-guard-app/src/client/components/Dashboard/components/Summary.tsx
similarity index 69%
rename from src/client/components/Dashboard/components/Summary.tsx
rename to flake-guard-app/src/client/components/Dashboard/components/Summary.tsx
index 45ae985..0d042a7 100644
--- a/src/client/components/Dashboard/components/Summary.tsx
+++ b/flake-guard-app/src/client/components/Dashboard/components/Summary.tsx
@@ -30,6 +30,7 @@ const Summary: React.FC = ({metrics}) => {
},
],
};
+
let total = 0;
if (metrics) {
const passed = metrics.passed;
@@ -45,22 +46,23 @@ const Summary: React.FC = ({metrics}) => {
pieChartData.labels[2] = `Skipped ${skipped}/${total}`;
}
-
//OPTIONS
const options = {
- cutout: '80%',
+ cutout: '90%',
responsive: true,
plugins: {
legend: {
position: 'bottom' as const,
+ align: 'center' as const,
labels: {
usePointStyle: true,
+ color: '#474646',
},
},
textInside: {
- text: `${total} tests`,
- color: 'grey',
- fontSize: 28,
+ text: `${total} assertions`,
+ color: '#474646',
+ fontSize: 20,
},
title: {
display: true,
@@ -69,18 +71,12 @@ const Summary: React.FC = ({metrics}) => {
size: 18,
},
},
- datalabels: {
- display: true,
- formatter: (value: any) => {
- console.log('value', value);
- return value;
- },
- },
},
};
- //Configuration of the plugin on line 60. Allows display the total of tests
- ChartJS.register({
+ //Configuration of the plugin from line 60. Allows display the total of assertions (text)
+ const textInsidePlugin = {
id: 'textInside',
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
afterDatasetsDraw: function (chart: any) {
const ctx = chart.ctx;
const width = chart.width;
@@ -95,28 +91,30 @@ const Summary: React.FC = ({metrics}) => {
const textY = Math.round(height / 2);
ctx.fillText(text, textX, textY);
},
- });
- const plugin = [
- {
- id: 'customCanvasBackgroundColor',
- beforeDraw: (
- chart: ChartJS<'doughnut'>,
- args: any,
- options: {color?: string}
- ) => {
- const {ctx} = chart;
- ctx.save();
- ctx.globalCompositeOperation = 'destination-over';
- ctx.fillStyle = options.color || '#ffffff';
- ctx.fillRect(0, 0, chart.width, chart.height);
- ctx.restore();
- },
+ };
+ const backgroundColorPlugin = {
+ id: 'customCanvasBackgroundColor',
+ beforeDraw: (
+ chart: ChartJS<'doughnut'>,
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ args: any,
+ options: {color?: string}
+ ) => {
+ const {ctx} = chart;
+ ctx.save();
+ ctx.globalCompositeOperation = 'destination-over';
+ ctx.fillStyle = options.color || '#ffffff';
+ ctx.fillRect(0, 0, chart.width, chart.height);
+ ctx.restore();
},
- ];
+ };
+ ChartJS.register(backgroundColorPlugin);
+
+ const plugins = [textInsidePlugin, backgroundColorPlugin];
return (
-
-
+
+
);
};
diff --git a/flake-guard-app/src/client/components/Dashboard/components/Trends.tsx b/flake-guard-app/src/client/components/Dashboard/components/Trends.tsx
new file mode 100644
index 0000000..86a1271
--- /dev/null
+++ b/flake-guard-app/src/client/components/Dashboard/components/Trends.tsx
@@ -0,0 +1,14 @@
+import React from 'react';
+
+const Trends: React.FC = () => {
+ return (
+
+
Trends
+
Flakiness
+
New Failures
+
Always Failing
+
+ );
+};
+
+export default Trends;
diff --git a/flake-guard-app/src/client/components/Dashboard/fakeData.ts b/flake-guard-app/src/client/components/Dashboard/fakeData.ts
new file mode 100644
index 0000000..0c35d37
--- /dev/null
+++ b/flake-guard-app/src/client/components/Dashboard/fakeData.ts
@@ -0,0 +1,307 @@
+// export const fakeData = [
+// {
+// "value": 317,
+// "day": "2017-07-27"
+// },
+// {
+// "value": 261,
+// "day": "2016-12-23"
+// },
+// {
+// "value": 3,
+// "day": "2017-04-30"
+// },
+// {
+// "value": 239,
+// "day": "2017-01-19"
+// },
+// {
+// "value": 129,
+// "day": "2016-04-17"
+// },
+// {
+// "value": 117,
+// "day": "2016-03-12"
+// },
+// {
+// "value": 3,
+// "day": "2016-01-29"
+// }
+// ]
+
+export const fakeData = [
+ {
+ created_at: '2024-07-16T12:09:16.030Z',
+ date: '2024-07-07',
+ id: '16',
+ results: {
+ metrics: [
+ {
+ fullName:
+ 'db unit tests #sync writes a valid marketList to the JSON file',
+ passed: 5,
+ failed: 0,
+ skipped: 0,
+ totalRuns: 5,
+ },
+ ],
+ },
+ user_id: 'c458792b-22a8-4f4d-b6ca-2df49a83748e',
+ },
+ {
+ created_at: '2024-07-16T12:09:16.030Z',
+ date: '2024-07-08',
+ id: '16',
+ results: {
+ metrics: [],
+ },
+ user_id: 'c458792b-22a8-4f4d-b6ca-2df49a83748e',
+ },
+ {
+ created_at: '2024-07-16T12:09:16.030Z',
+ date: '2024-07-09',
+ id: '16',
+ results: {
+ metrics: [
+ {
+ fullName:
+ 'db unit tests #sync writes a valid marketList to the JSON file',
+ passed: 5,
+ failed: 0,
+ skipped: 0,
+ totalRuns: 5,
+ },
+ {
+ fullName:
+ 'db unit tests #sync overwrites previously existing markets',
+ passed: 3,
+ failed: 2,
+ skipped: 0,
+ totalRuns: 5,
+ },
+ ],
+ },
+ user_id: 'c458792b-22a8-4f4d-b6ca-2df49a83748e',
+ },
+ {
+ created_at: '2024-07-16T12:09:16.030Z',
+ date: '2024-07-10',
+ id: '16',
+ results: {
+ metrics: [
+ {
+ fullName:
+ 'db unit tests #sync writes a valid marketList to the JSON file',
+ passed: 2,
+ failed: 2,
+ skipped: 1,
+ totalRuns: 5,
+ },
+ {
+ fullName:
+ 'db unit tests #sync overwrites previously existing markets',
+ passed: 2,
+ failed: 3,
+ skipped: 0,
+ totalRuns: 5,
+ },
+ ],
+ },
+ user_id: 'c458792b-22a8-4f4d-b6ca-2df49a83748e',
+ },
+
+ {
+ created_at: '2024-07-16T20:47:27.015Z',
+ date: '2024-07-11',
+ id: '21',
+ results: {
+ metrics: [
+ {
+ fullName:
+ 'db unit tests #sync writes a valid marketList to the JSON file',
+ passed: 2,
+ failed: 0,
+ skipped: 1,
+ totalRuns: 5,
+ },
+ {
+ fullName:
+ 'db unit tests #sync overwrites previously existing markets',
+ passed: 2,
+ failed: 0,
+ skipped: 0,
+ totalRuns: 5,
+ },
+ {
+ fullName:
+ 'db unit tests #sync writes a valid marketList to the JSON file',
+ passed: 20,
+ failed: 0,
+ skipped: 1,
+ totalRuns: 5,
+ },
+ ],
+ verbose: Array(2),
+ },
+ user_id: 'c458792b-22a8-4f4d-b6ca-2df49a83748e',
+ },
+ {
+ created_at: '2024-07-16T12:09:16.030Z',
+ date: '2024-07-12',
+ id: '16',
+ results: {
+ metrics: [
+ {
+ fullName:
+ 'db unit tests #sync writes a valid marketList to the JSON file',
+ passed: 5,
+ failed: 0,
+ skipped: 0,
+ totalRuns: 5,
+ },
+ {
+ fullName:
+ 'db unit tests #sync overwrites previously existing markets',
+ passed: 3,
+ failed: 2,
+ skipped: 0,
+ totalRuns: 5,
+ },
+ ],
+ },
+ user_id: 'c458792b-22a8-4f4d-b6ca-2df49a83748e',
+ },
+ {
+ created_at: '2024-07-16T12:09:16.030Z',
+ date: '2024-07-13',
+ id: '16',
+ results: {
+ metrics: [
+ {
+ fullName:
+ 'db unit tests #sync writes a valid marketList to the JSON file',
+ passed: 2,
+ failed: 2,
+ skipped: 1,
+ totalRuns: 5,
+ },
+ {
+ fullName:
+ 'db unit tests #sync overwrites previously existing markets',
+ passed: 2,
+ failed: 3,
+ skipped: 0,
+ totalRuns: 5,
+ },
+ ],
+ },
+ user_id: 'c458792b-22a8-4f4d-b6ca-2df49a83748e',
+ },
+
+];
+
+// //OLD FAKE DATA
+// // export const fakeData = [
+// // {
+// // fullName: 'db unit tests #sync writes a valid marketList to the JSON file',
+// // passed: 2,
+// // failed: 2,
+// // skipped: 1,
+// // totalRuns: 5,
+// // },
+// // {
+// // fullName: 'db unit tests #sync overwrites previously existing markets',
+// // passed: 2,
+// // failed: 3,
+// // skipped: 0,
+// // totalRuns: 5,
+// // },
+// // {
+// // fullName: 'db unit tests #sync writes a valid marketList to the JSON file',
+// // passed: 2,
+// // failed: 2,
+// // skipped: 1,
+// // totalRuns: 5,
+// // },
+// // {
+// // fullName: 'db unit tests #sync overwrites previously existing markets',
+// // passed: 2,
+// // failed: 3,
+// // skipped: 0,
+// // totalRuns: 5,
+// // },
+// // {
+// // fullName: 'db unit tests #sync writes a valid marketList to the JSON file',
+// // passed: 2,
+// // failed: 2,
+// // skipped: 1,
+// // totalRuns: 5,
+// // },
+// // {
+// // fullName: 'db unit tests #sync overwrites previously existing markets',
+// // passed: 2,
+// // failed: 3,
+// // skipped: 0,
+// // totalRuns: 5,
+// // },
+// // {
+// // fullName: 'db unit tests #sync writes a valid marketList to the JSON file',
+// // passed: 2,
+// // failed: 2,
+// // skipped: 1,
+// // totalRuns: 5,
+// // },
+// // {
+// // fullName: 'db unit tests #sync overwrites previously existing markets',
+// // passed: 2,
+// // failed: 3,
+// // skipped: 0,
+// // totalRuns: 5,
+// // },
+
+// // {
+// // fullName:
+// // 'db unit tests #find returns list of all markets from the json file',
+// // passed: 0,
+// // failed: 1,
+// // skipped: 0,
+// // },
+// // {
+// // fullName: 'db unit tests #find works if the list of markets is empty',
+// // passed: 0,
+// // failed: 1,
+// // skipped: 0,
+// // },
+// // {
+// // fullName: 'db unit tests #drop writes an empty array to the json file',
+// // passed: 0,
+// // failed: 0,
+// // skipped: 1,
+// // },
+// // {
+// // fullName:
+// // 'db unit tests #sync returns an error when cards value is not a number',
+// // passed: 1,
+// // failed: 0,
+// // skipped: 0,
+// // },
+// // {
+// // fullName:
+// // 'db unit tests #find returns list of all markets from the json file',
+// // passed: 0,
+// // failed: 1,
+// // skipped: 0,
+// // },
+// // {
+// // fullName: 'db unit tests #find works if the list of markets is empty',
+// // passed: 0,
+// // failed: 1,
+// // skipped: 0,
+// // },
+// // {
+// // fullName: 'db unit tests #drop writes an empty array to the json file',
+// // passed: 0,
+// // failed: 0,
+// // skipped: 1,
+// // },
+// // ];
diff --git a/flake-guard-app/src/client/components/Docs/DocPage.tsx b/flake-guard-app/src/client/components/Docs/DocPage.tsx
new file mode 100644
index 0000000..04e263c
--- /dev/null
+++ b/flake-guard-app/src/client/components/Docs/DocPage.tsx
@@ -0,0 +1,139 @@
+import React, {useState} from 'react';
+import Header from '../nav-bar';
+import Footer from '../footer';
+import '../../../styles/styles.css';
+import '../../../styles/docs.css';
+import npmLogo from '../../assets/npm-logo.png';
+import AcUnitIcon from '@mui/icons-material/AcUnit';
+import FAQ from './FAQ';
+
+const DocPage = (): JSX.Element => {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
What is FlakeGuard
+
+ FlakeGuard a free, open-source tool that allows developers to
+ run Jest tests to automatically detect, report, and manage flaky
+ tests in software development.
+
+
Features
+
+ Things you can do with FlakeGuard:
+
+ - Analytics dashboard
+ - Pass vs Fail colored table with flake severity
+ - Runtime metrics
+ - API Response times
+ - Offer recommendations to fix the flake
+ - Pointers to fail points within test code
+ - Running test order permutation
+ - Flagging tests using ‘async’ calls
+ -
+ Display flake improvement over time after using FlakeGuard
+
+
+
+
+
+
Installation
+
+ On your terminal run
+
+ npm i flake-guard
+
+ Installation as dev dependency:{' '}
+
+ npm i flake-guard --save-dev
+
+ To run FlakeGuard, simply execute the command{' '}
+ npx flake-guard {''}
with the name of the
+ test file that you want to examine. FlakeGuard will analyze your
+ E2E tests for flakiness by executing multiple test runs and
+ analyzing the results. The default number of test runs is 10,
+ but this can be adjusted as described below.
+ In general, there is a time versus accuracy tradeoff. More test
+ executions increase accuracy but reduce speed.
+
+
Configuration
+
+ To adjust FlakeGuard configuration variables, you can create a
+ file in the root directory of your project called{' '}
+ fg.config.json
. Below are the defaults, which can
+ be overridden in your local 'fg.config.json' file.
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export default DocPage;
diff --git a/flake-guard-app/src/client/components/Docs/FAQ.tsx b/flake-guard-app/src/client/components/Docs/FAQ.tsx
new file mode 100644
index 0000000..686d79b
--- /dev/null
+++ b/flake-guard-app/src/client/components/Docs/FAQ.tsx
@@ -0,0 +1,204 @@
+import React from 'react';
+import '../../../styles/docs.css';
+
+const Faq = (): JSX.Element => {
+ return (
+
+
+
+
+
+
+
+
+ A flaky test is a test that sometimes passes and sometimes fails
+ for the same code, often due to nondeterministic factors like
+ timing issues, network variability, or reliance on external
+ systems..
+
+
+
+
+
+
+
+
+
+ Flaky tests can be caused by race conditions, timing issues,
+ network instability, environmental dependencies, or inconsistent
+ external service responses.
+
+
+
+
+
+
+
+
+
+ You should use a FlakeGuard when you notice intermittent test
+ failures, to identify and address unstable tests, ensuring more
+ reliable and consistent test results.
+
+
+
+
+
+
+
+
+
+
+ Reduced Confidence:
+ They undermine trust in the test suite, as developers can't rely
+ on test results to reflect the true state of the code.
+
+
+ Wasted Time:
+ Developers spend time diagnosing and rerunning tests instead of
+ focusing on new features or bug fixes.
+
+
+ Masked Issues:
+ Real bugs might be overlooked or ignored, assuming test failures
+ are just flakiness.
+
+
+ Increased Maintenance:
+ More effort is needed to maintain and stabilize the test suite,
+ which can slow down development cycles.
+
+
+ Continuous Integration Disruptions:
+ They can cause interruptions in automated build and deployment
+ processes, delaying releases.
+
+
+
+
+
+
+
+
+
+
+ To prevent flaky tests, ensure tests are isolated to avoid shared
+ state dependencies. Control timing with fixed timeouts and avoid
+ relying on fluctuating conditions. Mock external dependencies for
+ consistent interactions and reduce variability. Use stable,
+ controlled test data to prevent unexpected input changes. Run
+ tests in a repeatable environment for consistency and
+ reproducibility, boosting confidence in results and accelerating
+ development cycles.
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Faq;
diff --git a/flake-guard-app/src/client/components/Docs/faq.test.tsx b/flake-guard-app/src/client/components/Docs/faq.test.tsx
new file mode 100644
index 0000000..46a3873
--- /dev/null
+++ b/flake-guard-app/src/client/components/Docs/faq.test.tsx
@@ -0,0 +1,15 @@
+/** @jest-environment jsdom */
+
+import * as React from 'react';
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import Faq from './FAQ';
+
+describe('FAQ', () => {
+ it('renders FAQ component', () => {
+ render (
)
+ })
+ it('should include all faq questions and answers', () => {
+ userEvent.click(screen.getByRole('button', {name: 'faq-button'}))
+ })
+});
diff --git a/flake-guard-app/src/client/components/FlakeRiskSign/FlakeRiskArrow.tsx b/flake-guard-app/src/client/components/FlakeRiskSign/FlakeRiskArrow.tsx
new file mode 100644
index 0000000..8f5fcf1
--- /dev/null
+++ b/flake-guard-app/src/client/components/FlakeRiskSign/FlakeRiskArrow.tsx
@@ -0,0 +1,59 @@
+import React from 'react';
+interface ArrowPositions {
+ low: number;
+ moderate: number;
+ high: number;
+ veryHigh: number;
+ extreme: number;
+}
+// import calculateFlakePercentage from '../Analytics/flake-percentage';
+
+const FlakeRiskArrow = (): JSX.Element => {
+ //const flakePercent = props.flakepercentage
+ const flakePercent: number = 1.0;
+
+ const positions: ArrowPositions = {
+ low: -72,
+ moderate: -36,
+ high: 0,
+ veryHigh: 36,
+ extreme: 72,
+ };
+
+ return (
+ <>
+ {flakePercent < 0.2 && (
+
+ )}
+ {flakePercent >= 0.2 && flakePercent < 0.3 && (
+
+ )}
+ {flakePercent >= 0.3 && flakePercent < 0.5 && (
+
+ )}
+ {flakePercent >= 0.5 && flakePercent <= 0.65 && (
+
+ )}
+ {flakePercent >= 0.65 && (
+
+ )}
+ >
+ );
+};
+
+export default FlakeRiskArrow;
diff --git a/src/client/components/FlakeRiskSign/FlakeRiskContainer.tsx b/flake-guard-app/src/client/components/FlakeRiskSign/FlakeRiskContainer.tsx
similarity index 100%
rename from src/client/components/FlakeRiskSign/FlakeRiskContainer.tsx
rename to flake-guard-app/src/client/components/FlakeRiskSign/FlakeRiskContainer.tsx
diff --git a/src/client/components/FlakeRiskSign/FlakeRiskSign.tsx b/flake-guard-app/src/client/components/FlakeRiskSign/FlakeRiskSign.tsx
similarity index 100%
rename from src/client/components/FlakeRiskSign/FlakeRiskSign.tsx
rename to flake-guard-app/src/client/components/FlakeRiskSign/FlakeRiskSign.tsx
diff --git a/src/client/components/FlakeRiskSign/FlakeRiskSlice.tsx b/flake-guard-app/src/client/components/FlakeRiskSign/FlakeRiskSlice.tsx
similarity index 100%
rename from src/client/components/FlakeRiskSign/FlakeRiskSlice.tsx
rename to flake-guard-app/src/client/components/FlakeRiskSign/FlakeRiskSlice.tsx
diff --git a/flake-guard-app/src/client/components/LandingPage/Advantages.tsx b/flake-guard-app/src/client/components/LandingPage/Advantages.tsx
new file mode 100644
index 0000000..2b839a5
--- /dev/null
+++ b/flake-guard-app/src/client/components/LandingPage/Advantages.tsx
@@ -0,0 +1,49 @@
+import React from 'react';
+import '../../../styles/advantages.css';
+import advantage1 from '../../assets/advantage1.png';
+import advantage2 from '../../assets/advantage2.png';
+import advantage3 from '../../assets/advantage3.png';
+import advantage4 from '../../assets/advantage4.png';
+
+const Advantages = (): JSX.Element => {
+ return (
+
+
+
+
Improved Test Reliability
+
+ By identifying flaky tests, FlakeGuard helps developers resolve
+ inconsistencies, leading to more reliable and trustworthy test suites.
+
+
+
+
+
Enhanced Developer Productivity
+
+ Detecting and fixing flaky tests saves troubleshooting time, letting
+ developers focus on writing quality code.
+
+
+
+
+
Increased Confidence in Software Releases
+
+ With fewer flaky tests, teams can be more confident in the results of
+ their test suites, ensuring that only stable and well-tested code is
+ released to production.
+
+
+
+
+
Faster Continuous Integration (CI) Pipelines
+
+ Reducing flaky tests minimizes false negatives in CI pipelines,
+ resulting in smoother, faster processes, and accelerating development
+ cycles.
+
+
+
+ );
+};
+
+export default Advantages;
diff --git a/flake-guard-app/src/client/components/LandingPage/advantages.test.tsx b/flake-guard-app/src/client/components/LandingPage/advantages.test.tsx
new file mode 100644
index 0000000..5723483
--- /dev/null
+++ b/flake-guard-app/src/client/components/LandingPage/advantages.test.tsx
@@ -0,0 +1,30 @@
+/** @jest-environment jsdom */
+
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import Advantages from './Advantages';
+
+describe('Advantages Component', () => {
+ it('should display "Improved Test Reliability" text', () => {
+ // Render the Advantages component
+ render(
);
+
+ // Check if the text "Improved Test Reliability" is in the document
+ const improvedReliability = screen.getByText('Improved Test Reliability');
+ expect(improvedReliability).toBeInTheDocument();
+ });
+
+ it('should display all advantages texts', () => {
+ // Render the Advantages component
+ render(
);
+
+ expect(screen.getByText('Improved Test Reliability')).toBeInTheDocument();
+ expect(screen.getByText('Enhanced Developer Productivity')).toBeInTheDocument();
+ expect(screen.getByText('Increased Confidence in Software Releases')).toBeInTheDocument();
+ expect(screen.getByText('Faster Continuous Integration (CI) Pipelines')).toBeInTheDocument();
+ expect(screen.getByTestId('paragraph1')).toBeInTheDocument();
+ expect(screen.getByTestId('paragraph2')).toBeInTheDocument();
+ expect(screen.getByTestId('paragraph3')).toBeInTheDocument();
+ });
+});
diff --git a/flake-guard-app/src/client/components/LandingPage/carousel.tsx b/flake-guard-app/src/client/components/LandingPage/carousel.tsx
new file mode 100644
index 0000000..441f1c7
--- /dev/null
+++ b/flake-guard-app/src/client/components/LandingPage/carousel.tsx
@@ -0,0 +1,58 @@
+// import React, {useState} from 'react';
+// import npmLogo from '../../assets/npm-logo.png';
+
+// //Carousel type??
+// // interface carouselTexts {
+// // string: string,
+// // string: string,
+// // string: string,
+// // }
+
+// // Array of carousel texts
+// const Carousel = (): JSX.Element => {
+// const [currentIndex, setCurrentIndex] = useState(0);
+// const carouselTexts = [
+// 'User Friendly UI with its own NPM package',
+// 'Easy installation process',
+// 'Simple Installation, recommended and utilized by Software Engineering teams',
+// ];
+
+// const handleNext = () => {
+// setCurrentIndex(prevIndex =>
+// prevIndex === carouselTexts.length - 1 ? 0 : prevIndex + 1
+// );
+// };
+
+// const handlePrev = () => {
+// setCurrentIndex(prevIndex =>
+// prevIndex === 0 ? carouselTexts.length - 1 : prevIndex - 1
+// );
+// };
+
+// return (
+// <>
+//
+//
+// {carouselTexts[currentIndex]}
+// {currentIndex === 1 && (
+//
+// )}
+//
+//
+//
+//
+//
+//
+// >
+// );
+// };
+
+// export default Carousel;
diff --git a/flake-guard-app/src/client/components/LandingPage/get-started.tsx b/flake-guard-app/src/client/components/LandingPage/get-started.tsx
new file mode 100644
index 0000000..22cc045
--- /dev/null
+++ b/flake-guard-app/src/client/components/LandingPage/get-started.tsx
@@ -0,0 +1,54 @@
+// import React from 'react';
+// import npmLogo from '../../assets/npm-logo.png';
+// import earthLightsLogo from '../../assets/earth.png';
+// import {Link} from 'react-router-dom';
+// import '../../../styles/styles.css';
+
+// const GetStarted = (): JSX.Element => {
+// return (
+//
+//
+//
+// Stability Starts Here:
+//
+// Master Test Flakiness Ensure Reliability
+//
+//
+//
+//
+//
+//
+// );
+// };
+
+// export default GetStarted;
diff --git a/flake-guard-app/src/client/components/LandingPage/landing-page.tsx b/flake-guard-app/src/client/components/LandingPage/landing-page.tsx
new file mode 100644
index 0000000..4a98a6a
--- /dev/null
+++ b/flake-guard-app/src/client/components/LandingPage/landing-page.tsx
@@ -0,0 +1,63 @@
+import React from 'react';
+import '../../../styles/styles.css';
+// import NavBarHeading from '../nav-bar';
+// import Carousel from './carousel';
+import logo from '../../assets/logo.png';
+import Header from '../nav-bar';
+import Advantages from './Advantages';
+import download from '../../assets/download.png';
+import Footer from '../footer';
+import {Link} from 'react-router-dom';
+import '../../../styles/landingPage.css';
+
+const Landing = (): JSX.Element => {
+ return (
+ <>
+
+
+
+
Save Thousands of Debugging Hours
+
+
+ Flake-Guard is a free, open-source tool that allows developers to
+ run Jest tests to automatically detect, report, and manage flaky
+ tests in software development.
+
+
+ By identifying and handling these inconsistent tests, Flake-Guard
+ helps maintain test reliability.
+
+
+
+ To learn more about FlakeGuard, click{' '}
+
+ here
+
+ .
+
+
+
+
PLACEHOLDER (graph)
+
+
+ PLACEHOLDER (extra info)
+
+
+
+ >
+ );
+};
+
+export default Landing;
diff --git a/flake-guard-app/src/client/components/Login/LoginButton.tsx b/flake-guard-app/src/client/components/Login/LoginButton.tsx
new file mode 100644
index 0000000..6640408
--- /dev/null
+++ b/flake-guard-app/src/client/components/Login/LoginButton.tsx
@@ -0,0 +1,102 @@
+import {useState, useEffect} from 'react';
+import {Link} from 'react-router-dom';
+import {supabaseClient} from '../../supabaseClient';
+import signout from '../../assets/signout.png';
+import github from '../../assets/github-mark-white.png';
+
+interface User {
+ email: string;
+ profile: string;
+}
+
+const LoginButton: React.FC = () => {
+ const [user, setUser] = useState
(null);
+ const [authChecked, setAuthChecked] = useState(false);
+
+ useEffect(() => {
+ checkUser();
+ window.addEventListener('hashchange', () => {
+ checkUser();
+ });
+ }, []);
+
+ async function checkUser() {
+ try {
+ const {data, error} = await supabaseClient.auth.getUser();
+ if (!error && data.user) {
+ console.log('USER DATA --->', data.user);
+
+ setUser({
+ email: data.user.user_metadata.user_name,
+ profile: data.user.user_metadata.avatar_url,
+ });
+ } else {
+ setUser(null);
+ }
+ setAuthChecked(true);
+ } catch (error) {
+ console.error('Error fetching user:', error);
+ setUser(null);
+ }
+ }
+
+ async function signInWithGithub() {
+ try {
+ await supabaseClient.auth.signInWithOAuth({
+ provider: 'github',
+ });
+ } catch (error) {
+ console.error('Error signing in: ', error);
+ }
+ }
+
+ async function signOut() {
+ await supabaseClient.auth.signOut();
+ setUser(null);
+ }
+
+ if (user && authChecked) {
+ return (
+
+
+
+ -
+ {user.email}
+
+ -
+
+
+ Sign out
+
+
+
+
+ );
+ } else if (authChecked) {
+ return (
+
+
+
+ );
+ } else return <>>;
+};
+
+export default LoginButton;
diff --git a/flake-guard-app/src/client/components/Login/login-out.tsx b/flake-guard-app/src/client/components/Login/login-out.tsx
new file mode 100644
index 0000000..21789d3
--- /dev/null
+++ b/flake-guard-app/src/client/components/Login/login-out.tsx
@@ -0,0 +1,18 @@
+// import React from 'react';
+// import NavBarHeading from '../nav-bar';
+// import '../../../styles/styles.css';
+
+// const LoginOrOut: React.FC = () => {
+// return (
+// <>
+//
+//
+//
+//
+// >
+// );
+// };
+
+// export default LoginOrOut;
diff --git a/flake-guard-app/src/client/components/footer.tsx b/flake-guard-app/src/client/components/footer.tsx
new file mode 100644
index 0000000..8933f51
--- /dev/null
+++ b/flake-guard-app/src/client/components/footer.tsx
@@ -0,0 +1,38 @@
+import React from 'react';
+import {Link} from 'react-router-dom';
+import github from '../assets/github-mark-white.png';
+import npm from '../assets/npm.png';
+
+import '../../styles/footer.css';
+
+const Footer = (): JSX.Element => {
+ return (
+
+
+
+ Home
+ Blog
+ Contact Us
+ Our Team
+
+
+
+ );
+};
+
+export default Footer;
diff --git a/flake-guard-app/src/client/components/nav-bar.tsx b/flake-guard-app/src/client/components/nav-bar.tsx
new file mode 100644
index 0000000..2d83c61
--- /dev/null
+++ b/flake-guard-app/src/client/components/nav-bar.tsx
@@ -0,0 +1,47 @@
+import React, {useEffect, useState} from 'react';
+import {supabaseClient} from '../supabaseClient';
+import {Link} from 'react-router-dom';
+import logo from '../assets/logo.png';
+import LoginButton from './Login/LoginButton';
+import '../../styles/header.css';
+
+const NavBarHeading: React.FC = () => {
+ const [userId, setUserId] = useState(null);
+
+ useEffect(() => {
+ const isLoggedIn = async () => {
+ try {
+ const {data, error} = await supabaseClient.auth.getUser();
+ if (data && !error) {
+ setUserId(data.user.id);
+ }
+ } catch (error) {
+ console.error('Error checking user auth: ', error);
+ }
+ };
+ isLoggedIn();
+ }, []);
+
+ return (
+
+
+
+
+
+
Docs
+
Blog
+ {userId &&
Dashboard}
+
+
+
+
+
+ );
+};
+
+export default NavBarHeading;
diff --git a/flake-guard-app/src/client/newDashboard/components/BarChart.tsx b/flake-guard-app/src/client/newDashboard/components/BarChart.tsx
new file mode 100644
index 0000000..2d595a5
--- /dev/null
+++ b/flake-guard-app/src/client/newDashboard/components/BarChart.tsx
@@ -0,0 +1,137 @@
+// import {ResponsiveBar} from '@nivo/bar';
+// import {data} from './data';
+// import '../../../../styles/dashboard/charts.css';
+
+// interface DataItem {
+// country: string;
+// 'hot dog': number;
+// 'hot dogColor': string;
+// burger: number;
+// burgerColor: string;
+// sandwich: number;
+// sandwichColor: string;
+// kebab: number;
+// kebabColor: string;
+// fries: number;
+// friesColor: string;
+// donut: number;
+// donutColor: string;
+// [key: string]: number | string; // Allow additional properties
+// }
+
+// interface BarChartProps {
+// data: DataItem[];
+// }
+
+// const BarChart: React.FC = ({data}) => {
+// console.log('data barchart', data);
+// return (
+//
+//
+// e.id + ': ' + e.formattedValue + ' in country: ' + e.indexValue
+// }
+// />
+//
+// );
+// };
+
+// export default BarChart;
diff --git a/flake-guard-app/src/client/newDashboard/components/TestComponent.tsx b/flake-guard-app/src/client/newDashboard/components/TestComponent.tsx
new file mode 100644
index 0000000..0406ee7
--- /dev/null
+++ b/flake-guard-app/src/client/newDashboard/components/TestComponent.tsx
@@ -0,0 +1,21 @@
+// import React from "react";
+
+// interface DataItem {
+// value: number;
+// day: String;
+// }
+
+// interface MessageProps {
+// message: DataItem[]
+// }
+// const TestComponent: React.FC = ({message}) => {
+// console.log("message from Calendar", message)
+// return (
+//
+// hello
+//
+// )
+// }
+
+// export default TestComponent;
+
diff --git a/flake-guard-app/src/client/newDashboard/components/calendar/Calendar.tsx b/flake-guard-app/src/client/newDashboard/components/calendar/Calendar.tsx
new file mode 100644
index 0000000..1aba960
--- /dev/null
+++ b/flake-guard-app/src/client/newDashboard/components/calendar/Calendar.tsx
@@ -0,0 +1,51 @@
+import React from 'react';
+import {ResponsiveCalendar} from '@nivo/calendar';
+
+interface DataItem {
+ value: number;
+ day: string;
+}
+
+interface DataProps {
+ CalendarData: DataItem[];
+}
+
+const getColor = (value: number): string => {
+ if (value >= 100) return 'red';
+ if (value >= 50) return 'orange';
+ if (value >= 25) return '#e8c1a0';
+ return '#1fbd4f';
+};
+
+const Calendar: React.FC = ({CalendarData}) => {
+ // console.log('data from CalendarData', CalendarData);
+ return (
+
+ );
+};
+
+export default Calendar;
diff --git a/flake-guard-app/src/client/newDashboard/components/calendar/data.ts b/flake-guard-app/src/client/newDashboard/components/calendar/data.ts
new file mode 100644
index 0000000..c4cccf7
--- /dev/null
+++ b/flake-guard-app/src/client/newDashboard/components/calendar/data.ts
@@ -0,0 +1,63 @@
+export const CalendarData = [
+ {
+ value: 15, //percentage of flaky tests that day?
+ day: '2018-01-13',
+ },
+ {
+ value: 197,
+ day: '2015-07-30',
+ },
+ {
+ value: 316,
+ day: '2016-09-27',
+ },
+ {
+ value: 154,
+ day: '2015-12-12',
+ },
+ {
+ value: 192,
+ day: '2016-10-17',
+ },
+ {
+ value: 57,
+ day: '2015-04-12',
+ },
+
+ {
+ value: 23,
+ day: '2016-10-13',
+ },
+ {
+ value: 349,
+ day: '2015-12-29',
+ },
+ {
+ value: 169,
+ day: '2015-10-01',
+ },
+ {
+ value: 292,
+ day: '2015-12-06',
+ },
+ {
+ value: 145,
+ day: '2015-10-10',
+ },
+ {
+ value: 387,
+ day: '2015-05-07',
+ },
+ {
+ value: 295,
+ day: '2016-05-15',
+ },
+ {
+ value: 154,
+ day: '2015-10-26',
+ },
+ {
+ value: 124,
+ day: '2018-03-27',
+ },
+];
diff --git a/flake-guard-app/src/client/newDashboard/components/calendar/index.tsx b/flake-guard-app/src/client/newDashboard/components/calendar/index.tsx
new file mode 100644
index 0000000..eead7fe
--- /dev/null
+++ b/flake-guard-app/src/client/newDashboard/components/calendar/index.tsx
@@ -0,0 +1,19 @@
+import React from 'react';
+import Sidebar from '../../global/Sidebar';
+import {CalendarData} from './data';
+import Calendar from './Calendar';
+import '../../../../styles/dashboard/charts.css'
+
+const CalendarPage: React.FC = () => {
+ // console.log('ata from indexedDB', CalendarData);
+ return (
+
+ );
+};
+
+export default CalendarPage;
diff --git a/flake-guard-app/src/client/newDashboard/components/line/LineChart.tsx b/flake-guard-app/src/client/newDashboard/components/line/LineChart.tsx
new file mode 100644
index 0000000..aaa4233
--- /dev/null
+++ b/flake-guard-app/src/client/newDashboard/components/line/LineChart.tsx
@@ -0,0 +1,88 @@
+// @ts-nocheck
+
+import React, {useEffect, useState} from 'react';
+import {ResponsiveLine} from '@nivo/line';
+import {lineChartParser} from '../../../utilities/lineChartParser';
+
+const LineChart: React.FC = ({results}) => {
+ const [lineChartData, setLineChartData] = useState([]);
+
+ useEffect(() => {
+ const chartData = lineChartParser(results);
+ console.log('Parsed Chart Data:', chartData);
+ if (Array.isArray(chartData)) setLineChartData(chartData);
+ }, [results]);
+
+ return (
+
+ );
+};
+
+export default LineChart;
+
diff --git a/flake-guard-app/src/client/newDashboard/components/line/data.ts b/flake-guard-app/src/client/newDashboard/components/line/data.ts
new file mode 100644
index 0000000..1f5e767
--- /dev/null
+++ b/flake-guard-app/src/client/newDashboard/components/line/data.ts
@@ -0,0 +1,272 @@
+export const linechartData = [
+ {
+ id: 'japan',
+ color: 'hsl(144, 70%, 50%)',
+ data: [
+ {
+ x: 'plane',
+ y: 109,
+ },
+ {
+ x: 'helicopter',
+ y: 187,
+ },
+ {
+ x: 'boat',
+ y: 257,
+ },
+ {
+ x: 'train',
+ y: 62,
+ },
+ {
+ x: 'subway',
+ y: 196,
+ },
+ {
+ x: 'bus',
+ y: 22,
+ },
+ {
+ x: 'car',
+ y: 204,
+ },
+ {
+ x: 'moto',
+ y: 179,
+ },
+ {
+ x: 'bicycle',
+ y: 124,
+ },
+ {
+ x: 'horse',
+ y: 224,
+ },
+ {
+ x: 'skateboard',
+ y: 100,
+ },
+ {
+ x: 'others',
+ y: 27,
+ },
+ ],
+ },
+ {
+ id: 'france',
+ color: 'hsl(175, 70%, 50%)',
+ data: [
+ {
+ x: 'plane',
+ y: 80,
+ },
+ {
+ x: 'helicopter',
+ y: 216,
+ },
+ {
+ x: 'boat',
+ y: 39,
+ },
+ {
+ x: 'train',
+ y: 276,
+ },
+ {
+ x: 'subway',
+ y: 217,
+ },
+ {
+ x: 'bus',
+ y: 276,
+ },
+ {
+ x: 'car',
+ y: 44,
+ },
+ {
+ x: 'moto',
+ y: 166,
+ },
+ {
+ x: 'bicycle',
+ y: 297,
+ },
+ {
+ x: 'horse',
+ y: 265,
+ },
+ {
+ x: 'skateboard',
+ y: 6,
+ },
+ {
+ x: 'others',
+ y: 239,
+ },
+ ],
+ },
+ {
+ id: 'us',
+ color: 'hsl(15, 70%, 50%)',
+ data: [
+ {
+ x: 'plane',
+ y: 54,
+ },
+ {
+ x: 'helicopter',
+ y: 209,
+ },
+ {
+ x: 'boat',
+ y: 42,
+ },
+ {
+ x: 'train',
+ y: 186,
+ },
+ {
+ x: 'subway',
+ y: 169,
+ },
+ {
+ x: 'bus',
+ y: 233,
+ },
+ {
+ x: 'car',
+ y: 250,
+ },
+ {
+ x: 'moto',
+ y: 175,
+ },
+ {
+ x: 'bicycle',
+ y: 276,
+ },
+ {
+ x: 'horse',
+ y: 193,
+ },
+ {
+ x: 'skateboard',
+ y: 81,
+ },
+ {
+ x: 'others',
+ y: 262,
+ },
+ ],
+ },
+ {
+ id: 'germany',
+ color: 'hsl(163, 70%, 50%)',
+ data: [
+ {
+ x: 'plane',
+ y: 201,
+ },
+ {
+ x: 'helicopter',
+ y: 48,
+ },
+ {
+ x: 'boat',
+ y: 69,
+ },
+ {
+ x: 'train',
+ y: 91,
+ },
+ {
+ x: 'subway',
+ y: 187,
+ },
+ {
+ x: 'bus',
+ y: 99,
+ },
+ {
+ x: 'car',
+ y: 198,
+ },
+ {
+ x: 'moto',
+ y: 44,
+ },
+ {
+ x: 'bicycle',
+ y: 295,
+ },
+ {
+ x: 'horse',
+ y: 106,
+ },
+ {
+ x: 'skateboard',
+ y: 274,
+ },
+ {
+ x: 'others',
+ y: 57,
+ },
+ ],
+ },
+ {
+ id: 'norway',
+ color: 'hsl(263, 70%, 50%)',
+ data: [
+ {
+ x: 'plane',
+ y: 192,
+ },
+ {
+ x: 'helicopter',
+ y: 50,
+ },
+ {
+ x: 'boat',
+ y: 243,
+ },
+ {
+ x: 'train',
+ y: 163,
+ },
+ {
+ x: 'subway',
+ y: 107,
+ },
+ {
+ x: 'bus',
+ y: 240,
+ },
+ {
+ x: 'car',
+ y: 263,
+ },
+ {
+ x: 'moto',
+ y: 290,
+ },
+ {
+ x: 'bicycle',
+ y: 236,
+ },
+ {
+ x: 'horse',
+ y: 17,
+ },
+ {
+ x: 'skateboard',
+ y: 179,
+ },
+ {
+ x: 'others',
+ y: 75,
+ },
+ ],
+ },
+];
diff --git a/flake-guard-app/src/client/newDashboard/components/line/index.tsx b/flake-guard-app/src/client/newDashboard/components/line/index.tsx
new file mode 100644
index 0000000..3430e99
--- /dev/null
+++ b/flake-guard-app/src/client/newDashboard/components/line/index.tsx
@@ -0,0 +1,23 @@
+import React, {useEffect} from 'react';
+import Sidebar from '../../global/Sidebar';
+import '../../../../styles/dashboard/charts.css';
+import LineChart from './LineChart';
+import {linechartData} from './data';
+import {lineChartParser} from '../../../utilities/lineChartParser';
+
+const LineChartPage: React.FC = () => {
+ console.log('linehcart -->', lineChartParser);
+
+ useEffect(() => {});
+
+ return (
+
+ );
+};
+
+export default LineChartPage;
diff --git a/flake-guard-app/src/client/newDashboard/components/pie/PieChart.tsx b/flake-guard-app/src/client/newDashboard/components/pie/PieChart.tsx
new file mode 100644
index 0000000..ee5e273
--- /dev/null
+++ b/flake-guard-app/src/client/newDashboard/components/pie/PieChart.tsx
@@ -0,0 +1,140 @@
+import React = require('react');
+import {ResponsivePie} from '@nivo/pie';
+
+interface DataItem {
+ id: string;
+ label: string;
+ value: number;
+ color: string;
+}
+
+interface DataProps {
+ piechartData: DataItem[];
+}
+
+const PieChart: React.FC = ({piechartData}) => {
+ // console.log('piechartData', piechartData);
+ return (
+ data.color}
+
+ margin={{top: 40, right: 80, bottom: 80, left: 80}}
+ innerRadius={0.5}
+ padAngle={0.7}
+ cornerRadius={3}
+ activeOuterRadiusOffset={8}
+ borderWidth={1}
+ borderColor={{
+ from: 'color',
+ modifiers: [['darker', 0.2]],
+ }}
+ arcLinkLabelsSkipAngle={10}
+ arcLinkLabelsTextColor="#333333"
+ arcLinkLabelsThickness={2}
+ arcLinkLabelsColor={{from: 'color'}}
+ arcLabelsSkipAngle={10}
+ arcLabelsTextColor={{
+ from: 'color',
+ modifiers: [['darker', 2]],
+ }}
+ defs={[
+ {
+ id: 'dots',
+ type: 'patternDots',
+ background: 'inherit',
+ color: 'rgba(255, 255, 255, 0.3)',
+ size: 4,
+ padding: 1,
+ stagger: true,
+ },
+ {
+ id: 'lines',
+ type: 'patternLines',
+ background: 'inherit',
+ color: 'rgba(255, 255, 255, 0.3)',
+ rotation: -45,
+ lineWidth: 6,
+ spacing: 10,
+ },
+ ]}
+ fill={[
+ {
+ match: {
+ id: 'ruby',
+ },
+ id: 'dots',
+ },
+ {
+ match: {
+ id: 'c',
+ },
+ id: 'dots',
+ },
+ {
+ match: {
+ id: 'go',
+ },
+ id: 'dots',
+ },
+ {
+ match: {
+ id: 'python',
+ },
+ id: 'dots',
+ },
+ {
+ match: {
+ id: 'scala',
+ },
+ id: 'lines',
+ },
+ {
+ match: {
+ id: 'lisp',
+ },
+ id: 'lines',
+ },
+ {
+ match: {
+ id: 'elixir',
+ },
+ id: 'lines',
+ },
+ {
+ match: {
+ id: 'javascript',
+ },
+ id: 'lines',
+ },
+ ]}
+ legends={[
+ {
+ anchor: 'bottom',
+ direction: 'row',
+ justify: false,
+ translateX: 0,
+ translateY: 56,
+ itemsSpacing: 0,
+ itemWidth: 100,
+ itemHeight: 18,
+ itemTextColor: '#999',
+ itemDirection: 'left-to-right',
+ itemOpacity: 1,
+ symbolSize: 18,
+ symbolShape: 'circle',
+ effects: [
+ {
+ on: 'hover',
+ style: {
+ itemTextColor: '#000',
+ },
+ },
+ ],
+ },
+ ]}
+ />
+ );
+};
+
+export default PieChart;
diff --git a/flake-guard-app/src/client/newDashboard/components/pie/data.ts b/flake-guard-app/src/client/newDashboard/components/pie/data.ts
new file mode 100644
index 0000000..7e54e1e
--- /dev/null
+++ b/flake-guard-app/src/client/newDashboard/components/pie/data.ts
@@ -0,0 +1,20 @@
+export const fakeData = [
+ {
+ id: 'passed',
+ label: 'passed',
+ value: 12,
+ color: ' hsl(134, 61%, 41%)',
+ },
+ {
+ id: 'failed',
+ label: 'failed',
+ value: 5,
+ color: '#f02e42',
+ },
+ {
+ id: 'skipped',
+ label: 'skipped',
+ value: 3,
+ color: 'rgb(181, 181, 181)',
+ },
+];
diff --git a/flake-guard-app/src/client/newDashboard/components/pie/index.tsx b/flake-guard-app/src/client/newDashboard/components/pie/index.tsx
new file mode 100644
index 0000000..b268eb2
--- /dev/null
+++ b/flake-guard-app/src/client/newDashboard/components/pie/index.tsx
@@ -0,0 +1,19 @@
+import React from 'react';
+import Sidebar from '../../global/Sidebar';
+import {fakeData} from './data';
+import '../../../../styles/dashboard/charts.css';
+import PieChart from './PieChart';
+
+const PieChartPage: React.FC = () => {
+ console.log('ata from indexedDB', fakeData);
+ return (
+
+ );
+};
+
+export default PieChartPage;
diff --git a/flake-guard-app/src/client/newDashboard/dashboard/NewUserDashboard.tsx b/flake-guard-app/src/client/newDashboard/dashboard/NewUserDashboard.tsx
new file mode 100644
index 0000000..6181ecc
--- /dev/null
+++ b/flake-guard-app/src/client/newDashboard/dashboard/NewUserDashboard.tsx
@@ -0,0 +1,154 @@
+// @ts-nocheck
+
+import React, {useEffect, useState} from 'react';
+import {useParams} from 'react-router-dom';
+import {api} from '../../services/index';
+import Sidebar from '../global/Sidebar';
+import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
+import '../../../styles/dashboard/newDashboard.css';
+import PieChart from '../components/pie/PieChart';
+import Calendar from '../components/calendar/Calendar';
+import {fakeData} from '../components/pie/data'; // data for PieChart
+import {CalendarData} from '../components/calendar/data'; // data for Calendar
+import LineChart from '../components/line/LineChart';
+import {flakyDataParser} from '../../utilities/flakyDataParser';
+import {OverlayTrigger, Tooltip, Button} from 'react-bootstrap';
+
+const NewUserDashboard: React.FC = () => {
+ const {userId} = useParams();
+ const [results, setResults] = useState([]);
+ const [flakytData, setFlakyData] = useState([]);
+
+ useEffect(() => {
+ const getResults = async () => {
+ try {
+ const results = await api.get(`/userDash/${userId}`);
+ const resultsArray = results.data;
+ // add a yyyy-mm-dd date to each result
+ for (const result of resultsArray) {
+ const ts = result.created_at;
+ result.date = ts.slice(0, ts.indexOf('T'));
+ }
+ // console.log('RESULTS USERDASH --->', resultsArray);
+ setResults(resultsArray);
+ } catch (error) {
+ console.log('Error getting results: ', error);
+ }
+ };
+ getResults();
+ }, [userId]);
+
+ // Data from utilities.js
+ useEffect(() => {
+ const chartData = flakyDataParser(results);
+ if (Array.isArray(chartData)) {
+ const latestRun = chartData[chartData.length - 1]; // outputs latest run
+ if (latestRun) {
+ // Adding leading zero to number less than 10
+ if (latestRun.flaky < 10) {
+ latestRun.flaky = latestRun.flaky.toString().padStart(2, '0');
+ latestRun.alwaysFail = latestRun.alwaysFail
+ .toString()
+ .padStart(2, '0');
+ latestRun.totalTests = latestRun.totalTests
+ .toString()
+ .padStart(2, '0');
+ }
+ }
+ setFlakyData(latestRun);
+ }
+ }, [results]);
+
+ console.log('flakyData', flakytData);
+
+ return (
+
+
+
+
DASHBOARD
+
+
+
+
+
+
Flakiness
+
+ Tests that occasionally fails without any changes in the
+ codebase, indicating unreliable behavior.
+
+ }
+ placement="right"
+ >
+
+
+
+
+
+ {flakytData && (
+
+
{flakytData.flaky}
+
+ {flakytData.flaky} out of {flakytData.totalTests} tests are
+ flaky
+
+
+ )}
+
+
+
+
Always failing
+
+ Consistently produces a failure result every time it is
+ executed, indicating a persistent issue in the code or
+ test setup.
+
+ }
+ placement="right"
+ >
+
+
+
+
+
+ {flakytData && (
+
+
{flakytData.alwaysFail}
+
+ {flakytData.alwaysFail} out of {flakytData.totalTests} tests
+ are always failing
+
+
+ )}
+
+
+
+
+
+
+
+ {/* BOTTOM CONTENT */}
+
+
+
+
+
+
+ );
+};
+
+export default NewUserDashboard;
diff --git a/flake-guard-app/src/client/newDashboard/global/Sidebar.tsx b/flake-guard-app/src/client/newDashboard/global/Sidebar.tsx
new file mode 100644
index 0000000..43c809f
--- /dev/null
+++ b/flake-guard-app/src/client/newDashboard/global/Sidebar.tsx
@@ -0,0 +1,106 @@
+import React from 'react';
+import {Sidebar, Menu, MenuItem} from 'react-pro-sidebar';
+import {Typography} from '@mui/material';
+import {Link} from 'react-router-dom';
+import HomeOutlinedIcon from '@mui/icons-material/HomeOutlined';
+import AnalyticsOutlinedIcon from '@mui/icons-material/AnalyticsOutlined';
+import PercentOutlinedIcon from '@mui/icons-material/PercentOutlined';
+import BugReportOutlinedIcon from '@mui/icons-material/BugReportOutlined';
+import PieChartOutlineOutlinedIcon from '@mui/icons-material/PieChartOutlineOutlined';
+import ShowChartOutlinedIcon from '@mui/icons-material/ShowChartOutlined';
+import BarChartOutlinedIcon from '@mui/icons-material/BarChartOutlined';
+import CalendarViewWeekOutlinedIcon from '@mui/icons-material/CalendarViewWeekOutlined';
+import HelpOutlineOutlinedIcon from '@mui/icons-material/HelpOutlineOutlined';
+import MenuBookOutlinedIcon from '@mui/icons-material/MenuBookOutlined';
+import '../../../styles/dashboard/sidebar.css'
+import logo from '../../assets/advantage1.png';
+
+const MenuSidebar: React.FC = () => {
+ return (
+
+
+
+
+
Paloma Reynolds
+
palomareynolds
+
+
+
+
+
+ Support
+
+
+
+ Documentation
+
+
+
+
+ );
+};
+
+export default MenuSidebar;
diff --git a/src/client/redux/fgSlice.ts b/flake-guard-app/src/client/redux/fgSlice.ts
similarity index 100%
rename from src/client/redux/fgSlice.ts
rename to flake-guard-app/src/client/redux/fgSlice.ts
diff --git a/src/client/redux/hooks.ts b/flake-guard-app/src/client/redux/hooks.ts
similarity index 100%
rename from src/client/redux/hooks.ts
rename to flake-guard-app/src/client/redux/hooks.ts
diff --git a/src/client/redux/store.ts b/flake-guard-app/src/client/redux/store.ts
similarity index 100%
rename from src/client/redux/store.ts
rename to flake-guard-app/src/client/redux/store.ts
diff --git a/src/client/redux/userSlice.ts b/flake-guard-app/src/client/redux/userSlice.ts
similarity index 89%
rename from src/client/redux/userSlice.ts
rename to flake-guard-app/src/client/redux/userSlice.ts
index 3d860f6..32cf2b5 100644
--- a/src/client/redux/userSlice.ts
+++ b/flake-guard-app/src/client/redux/userSlice.ts
@@ -1,5 +1,4 @@
import {createSlice} from '@reduxjs/toolkit';
-import {supabaseClient} from '../supabaseClient';
// import type {RootState} from './store';
interface User {
@@ -32,6 +31,10 @@ export const userSlice = createSlice({
// console.log('Error Fetching User:', error);
// }
// },
+ // userSignedIn: (state: User): void => {
+ // },
+ // userLoggedOut: (state: User): void => {
+ // }
},
});
diff --git a/src/client/services/index.tsx b/flake-guard-app/src/client/services/index.tsx
similarity index 100%
rename from src/client/services/index.tsx
rename to flake-guard-app/src/client/services/index.tsx
diff --git a/src/client/supabaseClient.ts b/flake-guard-app/src/client/supabaseClient.ts
similarity index 100%
rename from src/client/supabaseClient.ts
rename to flake-guard-app/src/client/supabaseClient.ts
diff --git a/flake-guard-app/src/client/types.ts b/flake-guard-app/src/client/types.ts
new file mode 100644
index 0000000..6ae56c6
--- /dev/null
+++ b/flake-guard-app/src/client/types.ts
@@ -0,0 +1,20 @@
+// src/client/types.ts
+export interface TestResult {
+ name: string;
+ passed: number;
+ failed: number;
+ skipped: number;
+}
+
+export interface AssertionResult {
+ name: string;
+ passed: boolean;
+ failed: boolean;
+}
+
+export interface MetricsData {
+ fullName: string;
+ passed: number;
+ failed: number;
+ skipped: number;
+}
diff --git a/flake-guard-app/src/client/utilities/flakyDataParser.ts b/flake-guard-app/src/client/utilities/flakyDataParser.ts
new file mode 100644
index 0000000..a42506f
--- /dev/null
+++ b/flake-guard-app/src/client/utilities/flakyDataParser.ts
@@ -0,0 +1,77 @@
+export interface FG {
+ created_at: string;
+ date: string;
+ id: string;
+ user_id: string;
+ results: Result;
+}
+
+interface Result {
+ metrics: Array;
+ verbose: Array