@spinnaker/kayenta 0.0.0-2025.1-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (289) hide show
  1. package/.editorconfig +9 -0
  2. package/.eslintrc.js +1 -0
  3. package/.huskyrc +5 -0
  4. package/.lintstagedrc.json +4 -0
  5. package/.prettierignore +4 -0
  6. package/.prettierrc.js +1 -0
  7. package/LICENSE.txt +203 -0
  8. package/README.md +81 -0
  9. package/__mocks__/styleMock.js +1 -0
  10. package/__mocks__/version.json +4 -0
  11. package/babel.config.js +3 -0
  12. package/build.gradle +67 -0
  13. package/build_scripts/checkLicenses.js +79 -0
  14. package/gradle.properties +0 -0
  15. package/jest.config.js +204 -0
  16. package/jest.setup.js +5 -0
  17. package/package.json +166 -0
  18. package/rollup-plugin-angularjs-template-loader.js +82 -0
  19. package/rollup.config.js +30 -0
  20. package/src/index.ts +2 -0
  21. package/src/kayenta/actions/creators.ts +163 -0
  22. package/src/kayenta/actions/index.ts +98 -0
  23. package/src/kayenta/canary.dataSource.bridge.ts +53 -0
  24. package/src/kayenta/canary.dataSource.stub.ts +64 -0
  25. package/src/kayenta/canary.help.ts +136 -0
  26. package/src/kayenta/canary.less +168 -0
  27. package/src/kayenta/canary.settings.ts +26 -0
  28. package/src/kayenta/canary.tsx +67 -0
  29. package/src/kayenta/components/canaryScore.component.less +77 -0
  30. package/src/kayenta/components/canaryScore.component.ts +12 -0
  31. package/src/kayenta/components/canaryScore.tsx +63 -0
  32. package/src/kayenta/components/canaryScores.component.ts +20 -0
  33. package/src/kayenta/components/canaryScores.less +22 -0
  34. package/src/kayenta/components/canaryScores.tsx +163 -0
  35. package/src/kayenta/components/loadStates.tsx +52 -0
  36. package/src/kayenta/domain/ICanaryConfig.ts +57 -0
  37. package/src/kayenta/domain/ICanaryConfigSummary.ts +7 -0
  38. package/src/kayenta/domain/ICanaryConfigUpdateResponse.ts +3 -0
  39. package/src/kayenta/domain/ICanaryExecutionStatusResult.ts +72 -0
  40. package/src/kayenta/domain/ICanaryJudgeResult.ts +51 -0
  41. package/src/kayenta/domain/ICanaryJudgeResultSummary.ts +5 -0
  42. package/src/kayenta/domain/ICanaryScoreThresholds.ts +4 -0
  43. package/src/kayenta/domain/IJudge.ts +5 -0
  44. package/src/kayenta/domain/IKayentaAccount.ts +14 -0
  45. package/src/kayenta/domain/IKayentaStageConfig.ts +58 -0
  46. package/src/kayenta/domain/IMetricSetPair.ts +17 -0
  47. package/src/kayenta/domain/IMetricsServiceMetadata.ts +2 -0
  48. package/src/kayenta/domain/ISetupCanaryStage.ts +11 -0
  49. package/src/kayenta/domain/MetricClassificationLabel.ts +8 -0
  50. package/src/kayenta/domain/ScoreClassificationLabel.ts +7 -0
  51. package/src/kayenta/domain/index.ts +15 -0
  52. package/src/kayenta/edit/changeMetricGroupModal.tsx +107 -0
  53. package/src/kayenta/edit/configDetail.tsx +31 -0
  54. package/src/kayenta/edit/configDetailActionButtons.tsx +24 -0
  55. package/src/kayenta/edit/configDetailHeader.tsx +104 -0
  56. package/src/kayenta/edit/configDetailLoadStates.tsx +36 -0
  57. package/src/kayenta/edit/configDetailLoader.tsx +60 -0
  58. package/src/kayenta/edit/configJson.less +42 -0
  59. package/src/kayenta/edit/configJsonModal.tsx +158 -0
  60. package/src/kayenta/edit/configList.less +7 -0
  61. package/src/kayenta/edit/configList.tsx +57 -0
  62. package/src/kayenta/edit/copyConfigButton.tsx +34 -0
  63. package/src/kayenta/edit/createConfigButton.tsx +34 -0
  64. package/src/kayenta/edit/deleteModal.tsx +87 -0
  65. package/src/kayenta/edit/edit.tsx +24 -0
  66. package/src/kayenta/edit/editMetricEffectSizes.tsx +186 -0
  67. package/src/kayenta/edit/editMetricModal.less +9 -0
  68. package/src/kayenta/edit/editMetricModal.spec.tsx +129 -0
  69. package/src/kayenta/edit/editMetricModal.tsx +294 -0
  70. package/src/kayenta/edit/editMetricValidation.spec.ts +63 -0
  71. package/src/kayenta/edit/editMetricValidation.ts +50 -0
  72. package/src/kayenta/edit/filterTemplateSelector.less +15 -0
  73. package/src/kayenta/edit/filterTemplateSelector.spec.tsx +106 -0
  74. package/src/kayenta/edit/filterTemplateSelector.tsx +194 -0
  75. package/src/kayenta/edit/filterTemplatesValidation.spec.ts +108 -0
  76. package/src/kayenta/edit/filterTemplatesValidation.ts +95 -0
  77. package/src/kayenta/edit/footer.less +30 -0
  78. package/src/kayenta/edit/footer.tsx +12 -0
  79. package/src/kayenta/edit/groupName.tsx +80 -0
  80. package/src/kayenta/edit/groupTabs.tsx +106 -0
  81. package/src/kayenta/edit/groupWeight.tsx +80 -0
  82. package/src/kayenta/edit/groupWeights.tsx +38 -0
  83. package/src/kayenta/edit/inlineTemplateEditor.spec.tsx +42 -0
  84. package/src/kayenta/edit/inlineTemplateEditor.tsx +61 -0
  85. package/src/kayenta/edit/judgeSelect.tsx +82 -0
  86. package/src/kayenta/edit/metricConfigurerDelegator.tsx +30 -0
  87. package/src/kayenta/edit/metricList.less +21 -0
  88. package/src/kayenta/edit/metricList.tsx +215 -0
  89. package/src/kayenta/edit/metricStoreSelector.tsx +66 -0
  90. package/src/kayenta/edit/nameAndDescription.tsx +90 -0
  91. package/src/kayenta/edit/openConfigJsonModalButton.tsx +33 -0
  92. package/src/kayenta/edit/openDeleteModalButton.tsx +50 -0
  93. package/src/kayenta/edit/ownedBy.tsx +34 -0
  94. package/src/kayenta/edit/save.tsx +19 -0
  95. package/src/kayenta/edit/saveConfigButton.tsx +65 -0
  96. package/src/kayenta/edit/saveConfigError.tsx +59 -0
  97. package/src/kayenta/edit/scoring.tsx +35 -0
  98. package/src/kayenta/edit/selectConfig.tsx +10 -0
  99. package/src/kayenta/edit/validationErrors.tsx +39 -0
  100. package/src/kayenta/index.ts +6 -0
  101. package/src/kayenta/layout/addNewButton.tsx +20 -0
  102. package/src/kayenta/layout/centeredDetail.tsx +13 -0
  103. package/src/kayenta/layout/deleteButton.tsx +11 -0
  104. package/src/kayenta/layout/disableable.tsx +87 -0
  105. package/src/kayenta/layout/formList.tsx +26 -0
  106. package/src/kayenta/layout/formRow.tsx +36 -0
  107. package/src/kayenta/layout/formattedDate.tsx +14 -0
  108. package/src/kayenta/layout/index.ts +2 -0
  109. package/src/kayenta/layout/keyValueList.less +20 -0
  110. package/src/kayenta/layout/keyValueList.tsx +114 -0
  111. package/src/kayenta/layout/list.less +9 -0
  112. package/src/kayenta/layout/list.spec.tsx +83 -0
  113. package/src/kayenta/layout/list.tsx +73 -0
  114. package/src/kayenta/layout/listDetail.tsx +33 -0
  115. package/src/kayenta/layout/radioChoice.tsx +29 -0
  116. package/src/kayenta/layout/styleguide.tsx +16 -0
  117. package/src/kayenta/layout/table/index.ts +5 -0
  118. package/src/kayenta/layout/table/nativeTable.tsx +51 -0
  119. package/src/kayenta/layout/table/nativeTableHeader.tsx +26 -0
  120. package/src/kayenta/layout/table/table.tsx +56 -0
  121. package/src/kayenta/layout/table/tableColumn.ts +7 -0
  122. package/src/kayenta/layout/table/tableHeader.tsx +23 -0
  123. package/src/kayenta/layout/tabs.tsx +26 -0
  124. package/src/kayenta/layout/titledSection.less +16 -0
  125. package/src/kayenta/layout/titledSection.tsx +20 -0
  126. package/src/kayenta/layout/titledSubsection.less +11 -0
  127. package/src/kayenta/layout/titledSubsection.tsx +22 -0
  128. package/src/kayenta/manualAnalysis/ManualAnalysisModal.tsx +716 -0
  129. package/src/kayenta/metricStore/atlas/atlasMetricConfigurer.tsx +130 -0
  130. package/src/kayenta/metricStore/atlas/index.ts +8 -0
  131. package/src/kayenta/metricStore/datadog/domain/IDatadogMetricDescriptor.ts +5 -0
  132. package/src/kayenta/metricStore/datadog/index.ts +9 -0
  133. package/src/kayenta/metricStore/datadog/metricConfigurer.tsx +90 -0
  134. package/src/kayenta/metricStore/datadog/metricTypeSelector.spec.tsx +59 -0
  135. package/src/kayenta/metricStore/datadog/metricTypeSelector.tsx +73 -0
  136. package/src/kayenta/metricStore/graphite/domain/IGraphiteMetricDescriptor.ts +5 -0
  137. package/src/kayenta/metricStore/graphite/index.ts +8 -0
  138. package/src/kayenta/metricStore/graphite/metricConfigurer.tsx +54 -0
  139. package/src/kayenta/metricStore/graphite/metricTypeSelector.tsx +80 -0
  140. package/src/kayenta/metricStore/graphite/typeahead.less +3 -0
  141. package/src/kayenta/metricStore/index.ts +8 -0
  142. package/src/kayenta/metricStore/metricStoreConfig.service.ts +12 -0
  143. package/src/kayenta/metricStore/newrelic/domain/INewRelicMetricDescriptor.ts +5 -0
  144. package/src/kayenta/metricStore/newrelic/index.ts +8 -0
  145. package/src/kayenta/metricStore/newrelic/metricConfigurer.tsx +58 -0
  146. package/src/kayenta/metricStore/prometheus/domain/IPrometheusCanaryMetricSetQueryConfig.ts +14 -0
  147. package/src/kayenta/metricStore/prometheus/domain/IPrometheusMetricDescriptor.ts +5 -0
  148. package/src/kayenta/metricStore/prometheus/index.ts +12 -0
  149. package/src/kayenta/metricStore/prometheus/metricConfigurer.tsx +157 -0
  150. package/src/kayenta/metricStore/prometheus/metricTypeSelector.less +5 -0
  151. package/src/kayenta/metricStore/prometheus/metricTypeSelector.spec.tsx +62 -0
  152. package/src/kayenta/metricStore/prometheus/metricTypeSelector.tsx +144 -0
  153. package/src/kayenta/metricStore/prometheus/queryTypeSelectors.spec.ts +61 -0
  154. package/src/kayenta/metricStore/prometheus/queryTypeSelectors.ts +38 -0
  155. package/src/kayenta/metricStore/signalfx/domain/ISignalFxCanaryMetricSetQueryConfig.ts +7 -0
  156. package/src/kayenta/metricStore/signalfx/index.ts +8 -0
  157. package/src/kayenta/metricStore/signalfx/metricConfigurer.less +10 -0
  158. package/src/kayenta/metricStore/signalfx/metricConfigurer.tsx +187 -0
  159. package/src/kayenta/metricStore/stackdriver/domain/IStackdriverCanaryMetricSetQueryConfig.ts +9 -0
  160. package/src/kayenta/metricStore/stackdriver/domain/IStackdriverMetricDescriptor.ts +17 -0
  161. package/src/kayenta/metricStore/stackdriver/index.ts +12 -0
  162. package/src/kayenta/metricStore/stackdriver/metricConfigurer.tsx +144 -0
  163. package/src/kayenta/metricStore/stackdriver/metricTypeSelector.spec.tsx +92 -0
  164. package/src/kayenta/metricStore/stackdriver/metricTypeSelector.tsx +113 -0
  165. package/src/kayenta/middleware/actionInterceptor.ts +29 -0
  166. package/src/kayenta/middleware/asyncDispatch.ts +37 -0
  167. package/src/kayenta/middleware/epics.ts +211 -0
  168. package/src/kayenta/middleware/index.ts +3 -0
  169. package/src/kayenta/navigation/canary.states.stub.ts +28 -0
  170. package/src/kayenta/navigation/canary.states.ts +182 -0
  171. package/src/kayenta/reducers/app.ts +56 -0
  172. package/src/kayenta/reducers/asyncRequest.ts +5 -0
  173. package/src/kayenta/reducers/data.ts +169 -0
  174. package/src/kayenta/reducers/editingTemplate.ts +54 -0
  175. package/src/kayenta/reducers/group.ts +82 -0
  176. package/src/kayenta/reducers/index.ts +245 -0
  177. package/src/kayenta/reducers/prometheusMetricConfig.spec.ts +33 -0
  178. package/src/kayenta/reducers/prometheusMetricConfig.ts +56 -0
  179. package/src/kayenta/reducers/selectedConfig.spec.ts +190 -0
  180. package/src/kayenta/reducers/selectedConfig.ts +566 -0
  181. package/src/kayenta/reducers/selectedRun.ts +101 -0
  182. package/src/kayenta/reducers/signalFxMetricConfig.ts +36 -0
  183. package/src/kayenta/reducers/stackdriverMetricConfig.spec.ts +33 -0
  184. package/src/kayenta/reducers/stackdriverMetricConfig.ts +41 -0
  185. package/src/kayenta/reducers/templates.spec.ts +192 -0
  186. package/src/kayenta/reducers/validators.ts +118 -0
  187. package/src/kayenta/report/detail/allMetricResultsHeader.tsx +32 -0
  188. package/src/kayenta/report/detail/clickableHeader.tsx +21 -0
  189. package/src/kayenta/report/detail/colors.ts +47 -0
  190. package/src/kayenta/report/detail/detail.less +16 -0
  191. package/src/kayenta/report/detail/detail.tsx +48 -0
  192. package/src/kayenta/report/detail/detailLoader.tsx +55 -0
  193. package/src/kayenta/report/detail/graph/graph.tsx +37 -0
  194. package/src/kayenta/report/detail/graph/metricSetPairGraph.service.ts +35 -0
  195. package/src/kayenta/report/detail/graph/semiotic/boxplot.less +45 -0
  196. package/src/kayenta/report/detail/graph/semiotic/boxplot.tsx +283 -0
  197. package/src/kayenta/report/detail/graph/semiotic/chartHeader.tsx +19 -0
  198. package/src/kayenta/report/detail/graph/semiotic/chartLegend.less +26 -0
  199. package/src/kayenta/report/detail/graph/semiotic/chartLegend.tsx +42 -0
  200. package/src/kayenta/report/detail/graph/semiotic/circleIcon.tsx +16 -0
  201. package/src/kayenta/report/detail/graph/semiotic/config.less +5 -0
  202. package/src/kayenta/report/detail/graph/semiotic/config.ts +38 -0
  203. package/src/kayenta/report/detail/graph/semiotic/customAxisTickLabel.tsx +17 -0
  204. package/src/kayenta/report/detail/graph/semiotic/declarations/labella.d.ts +16 -0
  205. package/src/kayenta/report/detail/graph/semiotic/declarations/react-container-dimensions.d.ts +3 -0
  206. package/src/kayenta/report/detail/graph/semiotic/declarations/semiotic.d.ts +160 -0
  207. package/src/kayenta/report/detail/graph/semiotic/differenceArea.less +17 -0
  208. package/src/kayenta/report/detail/graph/semiotic/differenceArea.tsx +186 -0
  209. package/src/kayenta/report/detail/graph/semiotic/histogram.less +22 -0
  210. package/src/kayenta/report/detail/graph/semiotic/histogram.tsx +251 -0
  211. package/src/kayenta/report/detail/graph/semiotic/index.tsx +19 -0
  212. package/src/kayenta/report/detail/graph/semiotic/noValidDataSign.less +5 -0
  213. package/src/kayenta/report/detail/graph/semiotic/noValidDataSign.tsx +10 -0
  214. package/src/kayenta/report/detail/graph/semiotic/secondaryTSXAxis.less +6 -0
  215. package/src/kayenta/report/detail/graph/semiotic/secondaryTSXAxis.tsx +58 -0
  216. package/src/kayenta/report/detail/graph/semiotic/semiotic.service.ts +32 -0
  217. package/src/kayenta/report/detail/graph/semiotic/semioticGraph.less +53 -0
  218. package/src/kayenta/report/detail/graph/semiotic/semioticGraph.tsx +49 -0
  219. package/src/kayenta/report/detail/graph/semiotic/timeSeries.less +42 -0
  220. package/src/kayenta/report/detail/graph/semiotic/timeSeries.tsx +473 -0
  221. package/src/kayenta/report/detail/graph/semiotic/tooltip.tsx +55 -0
  222. package/src/kayenta/report/detail/graph/semiotic/utils.ts +90 -0
  223. package/src/kayenta/report/detail/graphTypeSelector.less +4 -0
  224. package/src/kayenta/report/detail/graphTypeSelector.tsx +50 -0
  225. package/src/kayenta/report/detail/groupScores.tsx +68 -0
  226. package/src/kayenta/report/detail/header.less +70 -0
  227. package/src/kayenta/report/detail/header.tsx +39 -0
  228. package/src/kayenta/report/detail/headerArrow.tsx +13 -0
  229. package/src/kayenta/report/detail/loadStates.tsx +31 -0
  230. package/src/kayenta/report/detail/metricResultActions.less +29 -0
  231. package/src/kayenta/report/detail/metricResultActions.tsx +87 -0
  232. package/src/kayenta/report/detail/metricResultClassification.tsx +22 -0
  233. package/src/kayenta/report/detail/metricResultDetail.tsx +20 -0
  234. package/src/kayenta/report/detail/metricResultDetailLayout.tsx +19 -0
  235. package/src/kayenta/report/detail/metricResultDeviation.tsx +25 -0
  236. package/src/kayenta/report/detail/metricResultStats.less +9 -0
  237. package/src/kayenta/report/detail/metricResultStats.tsx +120 -0
  238. package/src/kayenta/report/detail/metricResults.less +12 -0
  239. package/src/kayenta/report/detail/metricResults.tsx +52 -0
  240. package/src/kayenta/report/detail/metricResultsClassificationFilters.tsx +65 -0
  241. package/src/kayenta/report/detail/metricResultsColumns.tsx +27 -0
  242. package/src/kayenta/report/detail/metricResultsList.less +44 -0
  243. package/src/kayenta/report/detail/metricResultsList.tsx +120 -0
  244. package/src/kayenta/report/detail/metricSetPairLoadStates.tsx +22 -0
  245. package/src/kayenta/report/detail/multipleResultsTable.tsx +81 -0
  246. package/src/kayenta/report/detail/reportException.tsx +57 -0
  247. package/src/kayenta/report/detail/reportExplanation.less +12 -0
  248. package/src/kayenta/report/detail/reportExplanation.tsx +32 -0
  249. package/src/kayenta/report/detail/reportMetadata.tsx +167 -0
  250. package/src/kayenta/report/detail/reportScores.less +47 -0
  251. package/src/kayenta/report/detail/reportScores.tsx +80 -0
  252. package/src/kayenta/report/detail/score.tsx +33 -0
  253. package/src/kayenta/report/detail/sourceLinks.tsx +69 -0
  254. package/src/kayenta/report/list/configLink.tsx +32 -0
  255. package/src/kayenta/report/list/executionList.less +7 -0
  256. package/src/kayenta/report/list/loadStates.tsx +32 -0
  257. package/src/kayenta/report/list/pipelineLink.tsx +15 -0
  258. package/src/kayenta/report/list/reportLink.tsx +33 -0
  259. package/src/kayenta/report/list/table.tsx +309 -0
  260. package/src/kayenta/report/report.tsx +11 -0
  261. package/src/kayenta/selectors/filterTemplatesSelectors.ts +87 -0
  262. package/src/kayenta/selectors/index.ts +62 -0
  263. package/src/kayenta/service/canaryConfig.service.ts +122 -0
  264. package/src/kayenta/service/canaryRun.service.ts +60 -0
  265. package/src/kayenta/service/delegateFactory.ts +24 -0
  266. package/src/kayenta/service/metricsServiceMetadata.service.ts +9 -0
  267. package/src/kayenta/stages/kayentaStage/AnalysisType.spec.tsx +47 -0
  268. package/src/kayenta/stages/kayentaStage/AnalysisType.tsx +49 -0
  269. package/src/kayenta/stages/kayentaStage/CanaryExecutionLabel.tsx +26 -0
  270. package/src/kayenta/stages/kayentaStage/analysisType.component.ts +12 -0
  271. package/src/kayenta/stages/kayentaStage/canaryRunSummaries.component.ts +12 -0
  272. package/src/kayenta/stages/kayentaStage/canaryRunSummaries.less +5 -0
  273. package/src/kayenta/stages/kayentaStage/canaryRunSummaries.tsx +136 -0
  274. package/src/kayenta/stages/kayentaStage/forAnalysisType.component.ts +45 -0
  275. package/src/kayenta/stages/kayentaStage/kayentaStage.controller.ts +789 -0
  276. package/src/kayenta/stages/kayentaStage/kayentaStage.html +528 -0
  277. package/src/kayenta/stages/kayentaStage/kayentaStage.less +5 -0
  278. package/src/kayenta/stages/kayentaStage/kayentaStage.transformer.ts +179 -0
  279. package/src/kayenta/stages/kayentaStage/kayentaStage.ts +221 -0
  280. package/src/kayenta/stages/kayentaStage/kayentaStageConfigSection.component.ts +21 -0
  281. package/src/kayenta/stages/kayentaStage/kayentaStageExecutionDetails.controller.ts +88 -0
  282. package/src/kayenta/stages/kayentaStage/kayentaStageExecutionDetails.html +114 -0
  283. package/src/kayenta/stages/kayentaStage/kayentaStageExecutionDetails.less +6 -0
  284. package/src/kayenta/stages/kayentaStage/stageTypes.ts +5 -0
  285. package/src/kayenta/utils/duration.spec.ts +69 -0
  286. package/src/kayenta/utils/duration.ts +48 -0
  287. package/src/lazy.ts +29 -0
  288. package/src/stub.ts +60 -0
  289. package/tsconfig.json +11 -0
@@ -0,0 +1,33 @@
1
+ import * as Actions from 'kayenta/actions';
2
+
3
+ import { stackdriverMetricConfigReducer } from './stackdriverMetricConfig';
4
+
5
+ describe('Reducer: stackdriverMetricConfigReducer', () => {
6
+ describe('update query field', () => {
7
+ it('updates arbitrary query key/value pairs on a query', () => {
8
+ const nextState = stackdriverMetricConfigReducer({ query: { crossSeriesReducer: 'REDUCE_NONE' } } as any, {
9
+ type: Actions.UPDATE_STACKDRIVER_METRIC_QUERY_FIELD,
10
+ payload: { field: 'crossSeriesReducer', value: 'REDUCE_MEAN' },
11
+ });
12
+ expect(nextState.query.crossSeriesReducer).toEqual('REDUCE_MEAN');
13
+ });
14
+
15
+ it('creates key/value pair if not already extant on the query', () => {
16
+ const nextState = stackdriverMetricConfigReducer({ query: {} } as any, {
17
+ type: Actions.UPDATE_STACKDRIVER_METRIC_QUERY_FIELD,
18
+ payload: { field: 'crossSeriesReducer', value: 'REDUCE_MEAN' },
19
+ });
20
+ expect(nextState.query.crossSeriesReducer).toEqual('REDUCE_MEAN');
21
+ });
22
+
23
+ it('deletes key/value pair if passed falsy value', () => {
24
+ [null, undefined, ''].forEach((falsyValue) => {
25
+ const nextState = stackdriverMetricConfigReducer({ query: { crossSeriesReducer: 'REDUCE_NONE' } } as any, {
26
+ type: Actions.UPDATE_STACKDRIVER_METRIC_QUERY_FIELD,
27
+ payload: { field: 'crossSeriesReducer', value: falsyValue },
28
+ });
29
+ expect(nextState.query.crossSeriesReducer).toBeUndefined();
30
+ });
31
+ });
32
+ });
33
+ });
@@ -0,0 +1,41 @@
1
+ import * as Actions from 'kayenta/actions';
2
+ import { IKayentaAction } from 'kayenta/actions/creators';
3
+ import { ICanaryMetricConfig } from 'kayenta/domain';
4
+ import { IUpdateListPayload, updateListReducer } from 'kayenta/layout/list';
5
+ import { IStackdriverCanaryMetricSetQueryConfig } from 'kayenta/metricStore/stackdriver/domain/IStackdriverCanaryMetricSetQueryConfig';
6
+ import { omit } from 'lodash';
7
+ import { Action } from 'redux';
8
+ import { handleActions } from 'redux-actions';
9
+
10
+ const updateGroupByReducer = updateListReducer();
11
+
12
+ type IStackdriverMetricConfig = ICanaryMetricConfig<IStackdriverCanaryMetricSetQueryConfig>;
13
+
14
+ export const stackdriverMetricConfigReducer = handleActions<IStackdriverMetricConfig, Action & any>(
15
+ {
16
+ [Actions.UPDATE_STACKDRIVER_GROUP_BY_FIELDS]: (
17
+ state: IStackdriverMetricConfig,
18
+ action: IKayentaAction<IUpdateListPayload>,
19
+ ) => ({
20
+ ...state,
21
+ query: {
22
+ ...state.query,
23
+ groupByFields: updateGroupByReducer(state.query.groupByFields || [], action),
24
+ },
25
+ }),
26
+ [Actions.UPDATE_STACKDRIVER_METRIC_QUERY_FIELD]: (state: IStackdriverMetricConfig, action: Action & any) => {
27
+ if (!action.payload.value) {
28
+ return {
29
+ ...state,
30
+ query: omit(state.query, action.payload.field),
31
+ };
32
+ }
33
+
34
+ return {
35
+ ...state,
36
+ query: { ...state.query, [action.payload.field]: action.payload.value, type: 'stackdriver' },
37
+ };
38
+ },
39
+ },
40
+ null,
41
+ );
@@ -0,0 +1,192 @@
1
+ import {
2
+ DELETE_TEMPLATE,
3
+ EDIT_TEMPLATE_BEGIN,
4
+ EDIT_TEMPLATE_CANCEL,
5
+ EDIT_TEMPLATE_CONFIRM,
6
+ EDIT_TEMPLATE_NAME,
7
+ EDIT_TEMPLATE_VALUE,
8
+ } from 'kayenta/actions';
9
+
10
+ import { selectedConfig as reducer } from './selectedConfig';
11
+
12
+ describe('Reducer: selectedConfig (templates)', () => {
13
+ const createAction = (type: string, payload: any = {}) => ({
14
+ type,
15
+ payload,
16
+ });
17
+
18
+ const runActionSequence = (...actions: any[]) => (initialState: any) =>
19
+ actions.reduce((s: any, a: any) => reducer(s, a), initialState);
20
+
21
+ describe('editing template name & value', () => {
22
+ let state: any;
23
+ beforeEach(() => {
24
+ state = {
25
+ config: {
26
+ templates: {
27
+ 'my-template': 'my-value',
28
+ 'my-other-template': 'my-other-value',
29
+ },
30
+ },
31
+ metricList: [
32
+ {
33
+ name: 'cpu_utilization',
34
+ query: {
35
+ customFilterTemplate: 'my-template',
36
+ metricType: 'compute.googleapis.com/instance/cpu_utilization',
37
+ },
38
+ },
39
+ ],
40
+ };
41
+ });
42
+
43
+ it('handles editing template name & value', () => {
44
+ state = runActionSequence(
45
+ createAction(EDIT_TEMPLATE_BEGIN, {
46
+ name: 'my-template',
47
+ value: 'my-value',
48
+ }),
49
+ createAction(EDIT_TEMPLATE_NAME, { name: 'my-edited-template' }),
50
+ createAction(EDIT_TEMPLATE_VALUE, {
51
+ value: 'resource.metadata.tag.my-custom-tag-1=${tag1} AND resource.metadata.tag.my-custom-tag-2=${tag2}',
52
+ }),
53
+ createAction(EDIT_TEMPLATE_CONFIRM),
54
+ )(state);
55
+
56
+ const {
57
+ config: { templates },
58
+ } = state;
59
+
60
+ expect(templates).toEqual({
61
+ 'my-edited-template':
62
+ 'resource.metadata.tag.my-custom-tag-1=${tag1} AND resource.metadata.tag.my-custom-tag-2=${tag2}',
63
+ 'my-other-template': 'my-other-value',
64
+ });
65
+ });
66
+
67
+ it('updates metrics to reflect template name changes', () => {
68
+ state = runActionSequence(
69
+ createAction(EDIT_TEMPLATE_BEGIN, {
70
+ name: 'my-template',
71
+ value: 'my-value',
72
+ }),
73
+ createAction(EDIT_TEMPLATE_NAME, { name: 'my-edited-template' }),
74
+ createAction(EDIT_TEMPLATE_CONFIRM),
75
+ )(state);
76
+
77
+ const { metricList } = state;
78
+ expect(metricList[0].query.customFilterTemplate).toEqual('my-edited-template');
79
+ });
80
+
81
+ it('leaves template name & value unchanged on EDIT_TEMPLATE_CANCEL', () => {
82
+ state = runActionSequence(
83
+ createAction(EDIT_TEMPLATE_BEGIN, {
84
+ name: 'my-template',
85
+ value: 'my-value',
86
+ }),
87
+ createAction(EDIT_TEMPLATE_NAME, { name: 'my-edited-template' }),
88
+ createAction(EDIT_TEMPLATE_VALUE, {
89
+ value: 'resource.metadata.tag.my-custom-tag-1=${tag1} ' + 'AND resource.metadata.tag.my-custom-tag-2=${tag2}',
90
+ }),
91
+ createAction(EDIT_TEMPLATE_CANCEL),
92
+ )(state);
93
+
94
+ const {
95
+ config: { templates },
96
+ } = state;
97
+
98
+ expect(templates).toEqual({
99
+ 'my-template': 'my-value',
100
+ 'my-other-template': 'my-other-value',
101
+ });
102
+ });
103
+ });
104
+
105
+ describe('template cleanup', () => {
106
+ it('clears temporary variables on EDIT_TEMPLATE_CONFIRM', () => {
107
+ const state: any = {
108
+ editingTemplate: {
109
+ name: 'my-template',
110
+ editedName: 'my-edited-template',
111
+ editedValue: '',
112
+ },
113
+ config: {
114
+ templates: {
115
+ 'my-template': '',
116
+ },
117
+ },
118
+ };
119
+
120
+ const {
121
+ editingTemplate: { name, editedName, editedValue },
122
+ } = reducer(state, createAction(EDIT_TEMPLATE_CONFIRM));
123
+
124
+ [name, editedName, editedValue].forEach((value) => expect(value).toEqual(null));
125
+ });
126
+
127
+ it('deletes a template', () => {
128
+ let state: any = {
129
+ config: {
130
+ templates: {
131
+ 'my-template': 'my-value',
132
+ 'my-other-template': 'my-other-value',
133
+ },
134
+ },
135
+ };
136
+
137
+ state = reducer(state, createAction(DELETE_TEMPLATE, { name: 'my-template' }));
138
+ expect(Object.keys(state.config.templates)).toEqual(['my-other-template']);
139
+
140
+ state = reducer(state, createAction(DELETE_TEMPLATE, { name: 'my-other-template' }));
141
+ expect(Object.keys(state.config.templates)).toEqual([]);
142
+
143
+ state = reducer(state, createAction(DELETE_TEMPLATE, { name: 'my-other-template' }));
144
+ expect(Object.keys(state.config.templates)).toEqual([]);
145
+ });
146
+
147
+ it('cleans up a newly added template on EDIT_TEMPLATE_CANCEL', () => {
148
+ let state: any = {
149
+ config: {
150
+ templates: {
151
+ 'my-template': 'my-value',
152
+ },
153
+ },
154
+ };
155
+
156
+ state = runActionSequence(
157
+ createAction(EDIT_TEMPLATE_BEGIN, { name: '', value: '' }),
158
+ createAction(EDIT_TEMPLATE_VALUE, { value: 'new-value' }),
159
+ createAction(EDIT_TEMPLATE_NAME, { name: 'new-name' }),
160
+ createAction(EDIT_TEMPLATE_CANCEL),
161
+ )(state);
162
+
163
+ expect(state.config.templates).toEqual({
164
+ 'my-template': 'my-value',
165
+ });
166
+ });
167
+ });
168
+
169
+ describe('creating a template', () => {
170
+ it('adds a new template', () => {
171
+ let state: any = {
172
+ config: {
173
+ templates: {
174
+ 'my-template': 'my-value',
175
+ },
176
+ },
177
+ };
178
+
179
+ state = runActionSequence(
180
+ createAction(EDIT_TEMPLATE_BEGIN, { name: '', value: '' }),
181
+ createAction(EDIT_TEMPLATE_VALUE, { value: 'new-value' }),
182
+ createAction(EDIT_TEMPLATE_NAME, { name: 'new-name' }),
183
+ createAction(EDIT_TEMPLATE_CONFIRM),
184
+ )(state);
185
+
186
+ expect(state.config.templates).toEqual({
187
+ 'my-template': 'my-value',
188
+ 'new-name': 'new-value',
189
+ });
190
+ });
191
+ });
192
+ });
@@ -0,0 +1,118 @@
1
+ import { KayentaAccountType } from 'kayenta/domain';
2
+ import { chain } from 'lodash';
3
+
4
+ import { ICanaryState } from './index';
5
+
6
+ export interface IConfigValidationError {
7
+ message: string;
8
+ }
9
+
10
+ type IConfigValidator = (state: ICanaryState) => IConfigValidationError;
11
+
12
+ const createValidationReducer = (validator: IConfigValidator) => {
13
+ return (state: ICanaryState) => {
14
+ if (!state.selectedConfig.config) {
15
+ return state;
16
+ }
17
+
18
+ const error = validator(state);
19
+ if (!error) {
20
+ return state;
21
+ }
22
+
23
+ return {
24
+ ...state,
25
+ selectedConfig: {
26
+ ...state.selectedConfig,
27
+ validationErrors: state.selectedConfig.validationErrors.concat(error ? [error] : []),
28
+ },
29
+ };
30
+ };
31
+ };
32
+
33
+ const isConfigNameUnique: IConfigValidator = (state) => {
34
+ const selectedConfig = state.selectedConfig.config;
35
+ const configSummaries = state.data.configSummaries;
36
+ const isUnique = configSummaries.every((s) => selectedConfig.name !== s.name || selectedConfig.id === s.id);
37
+
38
+ return isUnique ? null : { message: `Canary config '${selectedConfig.name}' already exists.` };
39
+ };
40
+
41
+ // See https://github.com/Netflix-Skunkworks/kayenta/blob/master/kayenta-web/src/main/java/com/netflix/kayenta/controllers/CanaryConfigController.java
42
+ const pattern = /^[a-zA-Z0-9_-]*$/;
43
+ const isConfigNameValid: IConfigValidator = (state) => {
44
+ const isValid = pattern.test(state.selectedConfig.config.name);
45
+ return isValid
46
+ ? null
47
+ : {
48
+ message: 'Canary config names must contain only letters, numbers, dashes (-) and underscores (_).',
49
+ };
50
+ };
51
+
52
+ const isGroupWeightsSumValid: IConfigValidator = (state) => {
53
+ const weights = Object.values(state.selectedConfig.group.groupWeights);
54
+ const sumOfWeights = weights.reduce((sum, weight) => sum + weight, 0);
55
+ const groupWeightsSumIsValid = weights.length === 0 || sumOfWeights === 100;
56
+
57
+ return groupWeightsSumIsValid ? null : { message: 'Metric group weights must sum to 100.' };
58
+ };
59
+
60
+ const isEveryGroupWeightValid: IConfigValidator = (state) => {
61
+ const everyGroupWeightIsValid = Object.values(state.selectedConfig.group.groupWeights).every((weight) => weight >= 0);
62
+
63
+ return everyGroupWeightIsValid ? null : { message: 'A group weight must be greater than or equal to 0.' };
64
+ };
65
+
66
+ const isEveryQueriedMetricStoreAvailable: IConfigValidator = (state) => {
67
+ const available = chain(state.data.kayentaAccounts.data)
68
+ .filter((account) => account.supportedTypes.includes(KayentaAccountType.MetricsStore))
69
+ .map((account) => account.metricsStoreType || account.type)
70
+ .uniq()
71
+ .valueOf();
72
+
73
+ const queried = chain(state.selectedConfig.metricList)
74
+ .map((metric) => metric.query.type)
75
+ .uniq()
76
+ .valueOf();
77
+
78
+ const unavailableQueried = queried.filter((store) => !available.includes(store));
79
+ let message: string;
80
+ if (unavailableQueried.length === 1) {
81
+ message = `This config contains metrics configured to query a metric
82
+ store (${unavailableQueried[0]})
83
+ that does not have any configured accounts.`;
84
+ } else if (unavailableQueried.length > 1) {
85
+ message = `This config contains metrics configured to query metric
86
+ stores (${unavailableQueried.join()})
87
+ that do not have any configured accounts.`;
88
+ }
89
+ return unavailableQueried.length ? { message } : null;
90
+ };
91
+
92
+ const areMultipleMetricStoresQueried: IConfigValidator = (state) => {
93
+ const queried = chain(state.selectedConfig.metricList)
94
+ .map((metric) => metric.query.type)
95
+ .uniq()
96
+ .valueOf();
97
+
98
+ return queried.length > 1 ? { message: 'All metrics must be from the same metric store.' } : null;
99
+ };
100
+
101
+ export const validationErrorsReducer = (state: ICanaryState): ICanaryState => {
102
+ if (!state.selectedConfig) {
103
+ return state;
104
+ }
105
+
106
+ if (!state.selectedConfig.validationErrors) {
107
+ state = { ...state, selectedConfig: { ...state.selectedConfig, validationErrors: [] } };
108
+ }
109
+
110
+ return [
111
+ isConfigNameUnique,
112
+ isConfigNameValid,
113
+ isGroupWeightsSumValid,
114
+ isEveryGroupWeightValid,
115
+ isEveryQueriedMetricStoreAvailable,
116
+ areMultipleMetricStoresQueried,
117
+ ].reduce((s, validator) => createValidationReducer(validator)(s), state);
118
+ };
@@ -0,0 +1,32 @@
1
+ import classNames from 'classnames';
2
+ import { ICanaryJudgeScore, ICanaryScoreThresholds } from 'kayenta/domain';
3
+ import * as React from 'react';
4
+
5
+ import { mapGroupToColor } from './colors';
6
+ import HeaderArrow from './headerArrow';
7
+
8
+ export interface IAllMetricResultsHeader {
9
+ onClick: () => void;
10
+ className: string;
11
+ score: ICanaryJudgeScore;
12
+ scoreThresholds: ICanaryScoreThresholds;
13
+ }
14
+
15
+ /*
16
+ * Clickable header for all metric results.
17
+ */
18
+ export default ({ className, onClick, score, scoreThresholds }: IAllMetricResultsHeader) => (
19
+ <section className={className}>
20
+ <div
21
+ onClick={onClick}
22
+ style={{
23
+ backgroundColor: mapGroupToColor(score, scoreThresholds),
24
+ }}
25
+ className={classNames('clickable', 'text-center', 'all-metric-results-header')}
26
+ >
27
+ <h3 className="heading-3 label">ALL</h3>
28
+ <HeaderArrow className="outer" arrowColor="var(--color-titanium)" />
29
+ <HeaderArrow className="inner" arrowColor={mapGroupToColor(score, scoreThresholds)} />
30
+ </div>
31
+ </section>
32
+ );
@@ -0,0 +1,21 @@
1
+ import classNames from 'classnames';
2
+ import * as React from 'react';
3
+
4
+ import HeaderArrow from './headerArrow';
5
+
6
+ export interface IGroupScoreProps {
7
+ label: string;
8
+ style?: { [key: string]: string };
9
+ onClick: () => void;
10
+ className: string;
11
+ }
12
+
13
+ /*
14
+ * Component for a labeled, clickable, colored header.
15
+ * */
16
+ export default ({ label, onClick, style, className }: IGroupScoreProps) => (
17
+ <section style={style} onClick={onClick} className={classNames('clickable', 'text-center', 'group-score', className)}>
18
+ <h3 className="heading-3 uppercase label">{label}</h3>
19
+ <HeaderArrow arrowColor={style.backgroundColor} />
20
+ </section>
21
+ );
@@ -0,0 +1,47 @@
1
+ import {
2
+ ICanaryJudgeGroupScore,
3
+ ICanaryJudgeScore,
4
+ ICanaryScoreThresholds,
5
+ MetricClassificationLabel,
6
+ ScoreClassificationLabel,
7
+ } from 'kayenta/domain';
8
+
9
+ // Standard Spinnaker styleguide colors.
10
+ const GREEN = 'var(--color-success)';
11
+ const RED = 'var(--color-danger)';
12
+ const GREY = 'var(--color-text-caption)';
13
+ const YELLOW = 'var(--color-warning)';
14
+
15
+ export const mapMetricClassificationToColor = (classification: MetricClassificationLabel): string =>
16
+ ({
17
+ [MetricClassificationLabel.High]: RED,
18
+ [MetricClassificationLabel.Low]: RED,
19
+ [MetricClassificationLabel.Error]: YELLOW,
20
+ [MetricClassificationLabel.Nodata]: GREY,
21
+ [MetricClassificationLabel.NodataFailMetric]: RED,
22
+ [MetricClassificationLabel.Pass]: GREEN,
23
+ }[classification]);
24
+
25
+ export const mapScoreClassificationToColor = (classification: ScoreClassificationLabel): string =>
26
+ ({
27
+ [ScoreClassificationLabel.Fail]: RED,
28
+ [ScoreClassificationLabel.Error]: YELLOW,
29
+ [ScoreClassificationLabel.Marginal]: GREY,
30
+ [ScoreClassificationLabel.Nodata]: GREY,
31
+ [ScoreClassificationLabel.Pass]: GREEN,
32
+ }[classification]);
33
+
34
+ export const mapGroupToColor = (
35
+ group: ICanaryJudgeGroupScore | ICanaryJudgeScore,
36
+ scoreThresholds: ICanaryScoreThresholds,
37
+ ): string => {
38
+ if (typeof group.score !== 'number') {
39
+ return YELLOW; // Some kind of error.
40
+ } else if (group.score > scoreThresholds.pass) {
41
+ return GREEN;
42
+ } else if (group.score > scoreThresholds.marginal) {
43
+ return GREY;
44
+ } else {
45
+ return RED;
46
+ }
47
+ };
@@ -0,0 +1,16 @@
1
+ .kayenta-overview-toggle {
2
+ height: 30px;
3
+ line-height: 30px;
4
+ cursor: pointer;
5
+ padding-right: 0 10px;
6
+
7
+ p {
8
+ height: 100%;
9
+ text-transform: uppercase;
10
+ margin: 0 10px;
11
+ }
12
+
13
+ span {
14
+ margin-bottom: 4px;
15
+ }
16
+ }
@@ -0,0 +1,48 @@
1
+ import { ICanaryExecutionStatusResult } from 'kayenta/domain';
2
+ import { ICanaryState } from 'kayenta/reducers';
3
+ import * as React from 'react';
4
+ import { connect } from 'react-redux';
5
+
6
+ import ReportHeader from './header';
7
+ import MetricResults from './metricResults';
8
+ import ReportException from './reportException';
9
+ import ReportExplanation from './reportExplanation';
10
+ import ReportScores from './reportScores';
11
+
12
+ import './detail.less';
13
+
14
+ /*
15
+ * Layout for report detail view.
16
+ * */
17
+
18
+ interface IDetailViewProps {
19
+ run: ICanaryExecutionStatusResult;
20
+ }
21
+
22
+ function DetailView({ run }: IDetailViewProps) {
23
+ const [isExpanded, setIsExpanded] = React.useState<boolean>(true);
24
+
25
+ if (!run.result?.judgeResult.results?.length && run.exception) {
26
+ return <ReportException />;
27
+ }
28
+
29
+ const toggleDetailHeader = () => {
30
+ setIsExpanded(!isExpanded);
31
+ };
32
+ return (
33
+ <>
34
+ <div className="vertical flex-1">
35
+ {isExpanded && <ReportHeader />}
36
+ <ReportExplanation />
37
+ <ReportScores isExpanded={isExpanded} toggleHeader={toggleDetailHeader} />
38
+ <MetricResults />
39
+ </div>
40
+ </>
41
+ );
42
+ }
43
+
44
+ const mapStateToProps = (state: ICanaryState) => ({
45
+ run: state.selectedRun.run,
46
+ });
47
+
48
+ export default connect(mapStateToProps)(DetailView);
@@ -0,0 +1,55 @@
1
+ import * as Creators from 'kayenta/actions/creators';
2
+ import { ICanaryState } from 'kayenta/reducers';
3
+ import * as React from 'react';
4
+ import { connect, Dispatch } from 'react-redux';
5
+ import { Observable } from 'rxjs/Observable';
6
+ import { Subscription } from 'rxjs/Subscription';
7
+
8
+ import ReportDetailLoadStates from './loadStates';
9
+
10
+ interface IResultLoaderStateParamsProps {
11
+ resultIdStream: Observable<IResultLoaderStateParams>;
12
+ }
13
+
14
+ interface IResultLoaderDispatchProps {
15
+ loadResult: (stateParams: IResultLoaderStateParams) => void;
16
+ }
17
+
18
+ interface IResultLoaderStateParams {
19
+ configId: string;
20
+ runId: string;
21
+ }
22
+
23
+ /*
24
+ * Top-level .reportDetail state component.
25
+ * Loads result details on changes to /report/:configId/:runId path parameter, renders load states.
26
+ */
27
+ class ResultDetailLoader extends React.Component<IResultLoaderDispatchProps & IResultLoaderStateParamsProps> {
28
+ private subscription: Subscription;
29
+
30
+ constructor(props: IResultLoaderDispatchProps & IResultLoaderStateParamsProps) {
31
+ super(props);
32
+ const { resultIdStream, loadResult } = props;
33
+ this.subscription = resultIdStream.subscribe(loadResult);
34
+ }
35
+
36
+ public componentWillUnmount(): void {
37
+ this.subscription.unsubscribe();
38
+ }
39
+
40
+ public render() {
41
+ return <ReportDetailLoadStates />;
42
+ }
43
+ }
44
+
45
+ const mapDispatchToProps = (dispatch: Dispatch<ICanaryState>): IResultLoaderDispatchProps => ({
46
+ loadResult: (stateParams: IResultLoaderStateParams) =>
47
+ dispatch(
48
+ Creators.loadRunRequest({
49
+ configId: stateParams.configId,
50
+ runId: stateParams.runId,
51
+ }),
52
+ ),
53
+ });
54
+
55
+ export default connect(null, mapDispatchToProps)(ResultDetailLoader);
@@ -0,0 +1,37 @@
1
+ import { ICanaryAnalysisResult } from 'kayenta/domain/ICanaryJudgeResult';
2
+ import { IMetricSetPair } from 'kayenta/domain/IMetricSetPair';
3
+ import { ICanaryState } from 'kayenta/reducers';
4
+ import { metricResultsSelector } from 'kayenta/selectors';
5
+ import * as React from 'react';
6
+ import { connect } from 'react-redux';
7
+
8
+ import { GraphType, metricSetPairGraphService } from './metricSetPairGraph.service';
9
+
10
+ interface IMetricSetPairGraphStateProps {
11
+ pair: IMetricSetPair;
12
+ result: ICanaryAnalysisResult;
13
+ graphType: GraphType;
14
+ }
15
+
16
+ const GRAPH_IMPLEMENTATIONS = ['semiotic'];
17
+
18
+ const MetricSetPairGraph = ({ pair, result, graphType }: IMetricSetPairGraphStateProps) => {
19
+ const delegates = GRAPH_IMPLEMENTATIONS.map((name) => metricSetPairGraphService.getDelegate(name)).filter((d) => !!d);
20
+ const delegate = delegates.find((candidate) => candidate.handlesGraphType(graphType));
21
+ if (!delegate) {
22
+ return <h3 className="heading-3">Could not load graph.</h3>;
23
+ }
24
+ const Graph = delegate.getGraph();
25
+ return <Graph metricSetPair={pair} result={result} type={graphType} />;
26
+ };
27
+
28
+ const mapStateToProps = (state: ICanaryState): IMetricSetPairGraphStateProps => {
29
+ const selectedMetric = state.selectedRun.selectedMetric;
30
+ return {
31
+ pair: state.selectedRun.metricSetPair.pair,
32
+ result: metricResultsSelector(state).find((result) => result.id === selectedMetric),
33
+ graphType: state.selectedRun.graphType,
34
+ };
35
+ };
36
+
37
+ export default connect(mapStateToProps)(MetricSetPairGraph);
@@ -0,0 +1,35 @@
1
+ import { ICanaryAnalysisResult, IMetricSetPair } from 'kayenta/domain';
2
+ import { buildDelegateService } from 'kayenta/service/delegateFactory';
3
+ import * as React from 'react';
4
+
5
+ // e.g., amplitude vs. time, histogram, etc.
6
+ export enum GraphType {
7
+ TimeSeries = 'Time Series',
8
+ Histogram = 'Histogram',
9
+ BoxPlot = 'Beeswarm Box Plot',
10
+ }
11
+
12
+ export interface IMetricSetPairGraphProps {
13
+ type: GraphType;
14
+ metricSetPair: IMetricSetPair;
15
+ result: ICanaryAnalysisResult;
16
+ }
17
+
18
+ export interface IMetricSetPairGraph {
19
+ /*
20
+ * Name of the graph implementation, referenced in settings.js.
21
+ * */
22
+ name: string;
23
+
24
+ /*
25
+ * Returns top-level graph component class.
26
+ * */
27
+ getGraph: () => React.ComponentType<IMetricSetPairGraphProps>;
28
+
29
+ /*
30
+ * Returns true if the graph implementation supports a given graph type.
31
+ * */
32
+ handlesGraphType: (type: GraphType) => boolean;
33
+ }
34
+
35
+ export const metricSetPairGraphService = buildDelegateService<IMetricSetPairGraph>();