@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,186 @@
1
+ import { scaleUtc } from 'd3-scale';
2
+ import { curveStepAfter } from 'd3-shape';
3
+ import * as React from 'react';
4
+ import { XYFrame } from 'semiotic';
5
+
6
+ import { vizConfig } from './config';
7
+ import CustomAxisTickLabel from './customAxisTickLabel';
8
+ import SecondaryTSXAxis from './secondaryTSXAxis';
9
+ import { IMargin, ISemioticChartProps } from './semiotic.service';
10
+ import * as utils from './utils';
11
+
12
+ import './differenceArea.less';
13
+
14
+ interface IDataPoint {
15
+ timestampMillis: number;
16
+ value: number;
17
+ }
18
+
19
+ interface IChartDataSet {
20
+ label: string;
21
+ color: string;
22
+ coordinates: IDataPoint[];
23
+ }
24
+
25
+ interface IInterimDataSet {
26
+ timestamp: number;
27
+ canary: number | string;
28
+ baseline: number | string;
29
+ }
30
+
31
+ interface IDifferenceAreaProps extends ISemioticChartProps {
32
+ millisSetBaseline: number[];
33
+ }
34
+
35
+ /*
36
+ * Supplemental visualization in the time series view to highlight
37
+ * Canary difference to baseline at any given timestamp
38
+ */
39
+ export default class DifferenceArea extends React.Component<IDifferenceAreaProps> {
40
+ private margin: IMargin = {
41
+ left: 60,
42
+ right: 20,
43
+ top: 8,
44
+ };
45
+
46
+ private chartHeight = 40; // chart height not including axes height
47
+ private headerHeight = 17;
48
+
49
+ private getChartData = () => {
50
+ const { metricSetPair } = this.props;
51
+ const {
52
+ values: { experiment, control },
53
+ scopes,
54
+ } = metricSetPair;
55
+
56
+ const stepMillis = scopes.control.stepMillis;
57
+ const maxDataPoints = Math.max(experiment.length, control.length);
58
+
59
+ // Align data sets in case canary & baseline have different lengths and/or starting time
60
+ const intDataSet: IInterimDataSet[] = Array(maxDataPoints)
61
+ .fill(0)
62
+ .map((_, i) => {
63
+ const e = experiment[i];
64
+ const c = control[i];
65
+ return {
66
+ timestamp: scopes.control.startTimeMillis + i * stepMillis,
67
+ canary: e,
68
+ baseline: c,
69
+ };
70
+ });
71
+
72
+ const baselineReferenceDataPoints: IDataPoint[] = intDataSet.map((ds: IInterimDataSet) => ({
73
+ timestampMillis: ds.timestamp,
74
+ value: 0,
75
+ }));
76
+
77
+ const differenceDataPoints: IDataPoint[] = intDataSet.map((ds: IInterimDataSet) => ({
78
+ timestampMillis: ds.timestamp,
79
+ value: typeof ds.canary === 'number' && typeof ds.baseline === 'number' ? ds.canary - ds.baseline : 0,
80
+ }));
81
+
82
+ return {
83
+ chartData: [
84
+ {
85
+ label: 'difference',
86
+ color: vizConfig.colors.canary,
87
+ coordinates: differenceDataPoints,
88
+ },
89
+ {
90
+ label: 'baselineReference',
91
+ color: vizConfig.colors.baseline,
92
+ coordinates: baselineReferenceDataPoints,
93
+ },
94
+ ],
95
+ };
96
+ };
97
+
98
+ private getSecondaryAxis = (millisOffset: number, millisSetBaseline: number[]) => {
99
+ const { parentWidth } = this.props;
100
+ const millisSetCanary = millisSetBaseline.map((ms: number) => ms + millisOffset);
101
+
102
+ return (
103
+ <SecondaryTSXAxis
104
+ margin={{ left: this.margin.left, right: this.margin.right, top: 0, bottom: 0 }}
105
+ width={parentWidth}
106
+ millisSet={millisSetCanary}
107
+ axisLabel={'canary'}
108
+ bottomOffset={0}
109
+ />
110
+ );
111
+ };
112
+
113
+ private getXAxisTotalHeight = (shouldUseSecondaryXAxis: boolean) => {
114
+ const { axisTickLineHeight, axisTickLabelHeight, axisLabelHeight } = vizConfig.timeSeries;
115
+
116
+ return shouldUseSecondaryXAxis
117
+ ? 2 * (axisLabelHeight + axisTickLabelHeight) + axisTickLineHeight
118
+ : axisTickLabelHeight;
119
+ };
120
+
121
+ public render() {
122
+ const { metricSetPair, parentWidth, millisSetBaseline } = this.props;
123
+
124
+ /*
125
+ * Generate the data needed for the graph components
126
+ */
127
+ const { scopes } = metricSetPair;
128
+ const { chartData } = this.getChartData();
129
+ const millisOffset = scopes.experiment.startTimeMillis - scopes.control.startTimeMillis;
130
+ const shouldUseSecondaryXAxis = millisOffset !== 0;
131
+
132
+ /*
133
+ * Build the visualization components
134
+ */
135
+ const xAxisTotalHeight = this.getXAxisTotalHeight(shouldUseSecondaryXAxis);
136
+ const computedConfig = {
137
+ lines: chartData,
138
+ size: [parentWidth, this.chartHeight + xAxisTotalHeight],
139
+ margin: { ...this.margin, bottom: xAxisTotalHeight },
140
+ lineType: {
141
+ type: 'area',
142
+ interpolator: curveStepAfter,
143
+ },
144
+ lineStyle: (ds: IChartDataSet) =>
145
+ ds.label === 'difference'
146
+ ? {
147
+ fill: ds.color,
148
+ fillOpacity: 0.6,
149
+ }
150
+ : {
151
+ stroke: ds.color,
152
+ strokeOpacity: 1,
153
+ strokeWidth: 2,
154
+ strokeDasharray: 5,
155
+ },
156
+ xAccessor: (d: IDataPoint) => new Date(d.timestampMillis),
157
+ yAccessor: 'value',
158
+ xScaleType: scaleUtc(),
159
+ axes: [
160
+ {
161
+ orient: 'left',
162
+ tickFormat: () => `\u0394 = 0`, // Δ = 0
163
+ tickValues: [0],
164
+ },
165
+ {
166
+ orient: 'bottom',
167
+ tickValues: utils.calculateDateTimeTicks(millisSetBaseline),
168
+ tickFormat: (d: number) => <CustomAxisTickLabel millis={d} />,
169
+ label: shouldUseSecondaryXAxis ? 'Baseline' : undefined,
170
+ className: shouldUseSecondaryXAxis ? 'baseline-dual-axis' : '',
171
+ },
172
+ ],
173
+ xExtent: [millisSetBaseline[0], millisSetBaseline[millisSetBaseline.length - 1]],
174
+ };
175
+
176
+ return (
177
+ <div className="difference-area">
178
+ <div className="chart-title" style={{ height: this.headerHeight }}>
179
+ Canary Value Differences from Baseline
180
+ </div>
181
+ <XYFrame {...computedConfig} />
182
+ {shouldUseSecondaryXAxis ? this.getSecondaryAxis(millisOffset, millisSetBaseline) : null}
183
+ </div>
184
+ );
185
+ }
186
+ }
@@ -0,0 +1,22 @@
1
+ .histogram {
2
+ .graph-container {
3
+ position: relative;
4
+ .bar-annotation {
5
+ text {
6
+ font-size: 12px;
7
+ font-weight: 600;
8
+ }
9
+ .annotation-connector {
10
+ display: none;
11
+ }
12
+ .note-line {
13
+ display: none;
14
+ }
15
+ }
16
+ g.ordinal-labels {
17
+ stroke: var(--color-text-primary);
18
+ font-size: 12px;
19
+ font-weight: 200;
20
+ }
21
+ }
22
+ }
@@ -0,0 +1,251 @@
1
+ import { extent, histogram } from 'd3-array';
2
+ import { scaleLinear } from 'd3-scale';
3
+ import * as React from 'react';
4
+ import {
5
+ Annotation,
6
+ IAnnotationType,
7
+ IOrFrameHoverArgs,
8
+ IOrGroup,
9
+ IOrPiece,
10
+ IOrXyData,
11
+ ISemioticAnnotationArgs,
12
+ OrdinalFrame,
13
+ } from 'semiotic';
14
+
15
+ import ChartHeader from './chartHeader';
16
+ import ChartLegend from './chartLegend';
17
+ import CircleIcon from './circleIcon';
18
+ import { vizConfig } from './config';
19
+ import { IMargin, ISemioticChartProps, ITooltip } from './semiotic.service';
20
+ import Tooltip from './tooltip';
21
+ import * as utils from './utils';
22
+
23
+ import './histogram.less';
24
+
25
+ interface IInputDataPoint {
26
+ value: number;
27
+ group: string;
28
+ }
29
+
30
+ interface IChartDataPoint {
31
+ group: string;
32
+ count: number;
33
+ x0: number;
34
+ x1: number;
35
+ }
36
+
37
+ interface IAnnotationData extends IChartDataPoint {
38
+ type: string;
39
+ }
40
+
41
+ interface IHistogramState {
42
+ tooltip: ITooltip;
43
+ }
44
+
45
+ export default class Histogram extends React.Component<ISemioticChartProps, IHistogramState> {
46
+ public state: IHistogramState = {
47
+ tooltip: null,
48
+ };
49
+
50
+ private margin: IMargin = {
51
+ top: 20,
52
+ bottom: 20,
53
+ left: 60,
54
+ right: 10,
55
+ };
56
+
57
+ private decorateData = (dataPoints: number[], group: string): IInputDataPoint[] => {
58
+ return dataPoints.map((dp) => ({
59
+ group,
60
+ value: dp,
61
+ }));
62
+ };
63
+
64
+ /*
65
+ * Semiotic actually supports histogram as a "summary type" out of the box, but the customization is
66
+ * currently limited (e.g. it can't display the y-axis ticks & labels)
67
+ * Hence we're manually generating histogram data using D3 and display it as
68
+ * a grouped bar chart in semiotic
69
+ */
70
+ private generateChartData = () => {
71
+ const { metricSetPair } = this.props;
72
+ const filterFunc = (v: IInputDataPoint) => typeof v.value === 'number';
73
+ const baselineInput = this.decorateData(metricSetPair.values.control, 'baseline');
74
+ const canaryInput = this.decorateData(metricSetPair.values.experiment, 'canary');
75
+ const combinedInput = baselineInput.concat(canaryInput).filter(filterFunc);
76
+
77
+ const x = scaleLinear()
78
+ .domain(extent(combinedInput.map((o) => o.value)))
79
+ .nice();
80
+ const domain = x.domain() as [number, number];
81
+
82
+ // create histogram bins based on the combined data points
83
+ const histogramData = histogram<IInputDataPoint, number>()
84
+ .domain(domain)
85
+ .value((d: IInputDataPoint) => d.value)(combinedInput);
86
+
87
+ const chartData: IChartDataPoint[] = [];
88
+
89
+ // Convert it to ordinal data format for bar chart in semiotic
90
+ histogramData.forEach((h) => {
91
+ const { x0, x1 } = h;
92
+ const baselineBin = { group: 'baseline', x0, x1, count: 0 };
93
+ const canaryBin = { group: 'canary', x0, x1, count: 0 };
94
+ h.forEach((d) => (d.group === 'baseline' ? baselineBin.count++ : canaryBin.count++));
95
+ chartData.push(baselineBin);
96
+ chartData.push(canaryBin);
97
+ });
98
+
99
+ return chartData;
100
+ };
101
+
102
+ // Function factory to handle hover event
103
+ private createChartHoverHandler = (chartData: IChartDataPoint[]) => {
104
+ return (d: IOrFrameHoverArgs<IChartDataPoint>): void => {
105
+ if (d && d.type === 'column-hover') {
106
+ const x1Max: number = Math.max(...chartData.map((cd: IChartDataPoint) => cd.x1));
107
+ const xyData = d.column.xyData;
108
+ const x = xyData[1].xy.x + this.margin.left;
109
+ const halfHeight1 = xyData[0].xy.height / 2;
110
+ const halfHeight2 = xyData[1].xy.height / 2;
111
+ const y = vizConfig.height - this.margin.bottom - Math.min(halfHeight1, halfHeight2);
112
+ const { x0, x1 } = d.summary[0].data;
113
+ const tooltipRows = d.summary.map((s: IOrPiece<IChartDataPoint>) => {
114
+ const { group, count } = s.data;
115
+ const valueStyle = {
116
+ fontWeight: 'bold',
117
+ } as React.CSSProperties;
118
+
119
+ return (
120
+ <div key={group}>
121
+ <CircleIcon group={group} />
122
+ <span>{` ${group} count: `}</span>
123
+ <span style={valueStyle}>{count}</span>
124
+ </div>
125
+ );
126
+ });
127
+
128
+ let label = `For metric value more than / equal to ${utils.formatMetricValue(x0)} `;
129
+ label +=
130
+ x1 === x1Max
131
+ ? `and less than / equal to ${utils.formatMetricValue(x1)}`
132
+ : `and less than ${utils.formatMetricValue(x1)}`;
133
+
134
+ const tooltipContent = (
135
+ <div>
136
+ <div>{label}</div>
137
+ {tooltipRows}
138
+ </div>
139
+ );
140
+
141
+ this.setState({
142
+ tooltip: {
143
+ content: tooltipContent,
144
+ x,
145
+ y,
146
+ },
147
+ });
148
+ } else {
149
+ this.setState({ tooltip: null });
150
+ }
151
+ };
152
+ };
153
+
154
+ // generate bar value annotations object
155
+ private defineAnnotations = (chartData: IChartDataPoint[]) => {
156
+ const annotations = [] as IAnnotationData[];
157
+ chartData.forEach((d: IChartDataPoint) => {
158
+ if (d.count > 0) {
159
+ annotations.push({
160
+ type: 'bar-value-custom',
161
+ ...d,
162
+ });
163
+ }
164
+ });
165
+ return annotations;
166
+ };
167
+
168
+ // function to actually create the JSX elements based on the annotation object
169
+ private customAnnotationFunction = (
170
+ args: ISemioticAnnotationArgs<IAnnotationData, IOrGroup<IChartDataPoint>>,
171
+ ): JSX.Element | null => {
172
+ const { d, i, categories } = args;
173
+ if (d.type === 'bar-value-custom') {
174
+ const { x, y, width } = categories[d.x1].xyData.find(
175
+ (c: IOrXyData<IChartDataPoint>) => c.piece.data.group === d.group,
176
+ ).xy;
177
+ const noteData = {
178
+ x: x + width / 2,
179
+ y,
180
+ nx: x + width / 2,
181
+ ny: y - 1,
182
+ note: {
183
+ label: `${d.count}`,
184
+ wrap: 100,
185
+ align: 'middle',
186
+ orientation: 'topBottom',
187
+ padding: 0,
188
+ color: vizConfig.colors[d.group],
189
+ lineType: 'horizontal',
190
+ },
191
+ className: `bar-annotation`,
192
+ };
193
+ return <Annotation key={i} noteData={noteData} />;
194
+ } else {
195
+ return null;
196
+ }
197
+ };
198
+
199
+ private getChartProps = () => {
200
+ const { parentWidth } = this.props;
201
+ const chartData = this.generateChartData();
202
+
203
+ return {
204
+ size: [parentWidth, vizConfig.height],
205
+ margin: this.margin,
206
+ projection: 'vertical',
207
+ type: 'clusterbar',
208
+ oLabel: (v: string) => <text textAnchor="middle">{utils.formatMetricValue(parseFloat(v))}</text>,
209
+ oPadding: 20,
210
+ oAccessor: (d: IChartDataPoint) => d.x1,
211
+ style: (d: IChartDataPoint) => {
212
+ return {
213
+ fill: vizConfig.colors[d.group],
214
+ strokeWidth: 1,
215
+ stroke: vizConfig.colors.background,
216
+ strokeOpacity: 1,
217
+ };
218
+ },
219
+ customHoverBehavior: this.createChartHoverHandler(chartData),
220
+ data: this.generateChartData(),
221
+ axis: [
222
+ {
223
+ orient: 'left',
224
+ label: 'measurement count',
225
+ tickFormat: (d: number) => (d === 0 ? null : Math.abs(d)),
226
+ },
227
+ ],
228
+ rAccessor: (d: IChartDataPoint) => d.count,
229
+ annotations: this.defineAnnotations(chartData),
230
+ svgAnnotationRules: this.customAnnotationFunction,
231
+ hoverAnnotation: [] as IAnnotationType[],
232
+ };
233
+ };
234
+
235
+ public render() {
236
+ const { metricSetPair } = this.props;
237
+
238
+ return (
239
+ <div className="histogram">
240
+ <ChartHeader metric={metricSetPair.name} />
241
+ <ChartLegend />
242
+ <div className="graph-container">
243
+ <div className="canary-chart">
244
+ <OrdinalFrame {...this.getChartProps()} />
245
+ </div>
246
+ <Tooltip {...this.state.tooltip} />
247
+ </div>
248
+ </div>
249
+ );
250
+ }
251
+ }
@@ -0,0 +1,19 @@
1
+ import * as React from 'react';
2
+ import { Suspense } from 'react';
3
+
4
+ import { Spinner } from '@spinnaker/core';
5
+
6
+ import { GraphType, IMetricSetPairGraphProps, metricSetPairGraphService } from '../metricSetPairGraph.service';
7
+ const SemioticGraphLazy = React.lazy(() => import(/* webpackChunkName: "Lazy-KayentaGraphs" */ './semioticGraph'));
8
+
9
+ const supportedGraphTypes: GraphType[] = [GraphType.TimeSeries, GraphType.Histogram, GraphType.BoxPlot];
10
+ // Semiotic component registration
11
+ metricSetPairGraphService.register({
12
+ name: 'semiotic',
13
+ handlesGraphType: (type) => supportedGraphTypes.includes(type),
14
+ getGraph: () => (props: IMetricSetPairGraphProps) => (
15
+ <Suspense fallback={<Spinner />}>
16
+ <SemioticGraphLazy {...props} />
17
+ </Suspense>
18
+ ),
19
+ });
@@ -0,0 +1,5 @@
1
+ .no-data-sign {
2
+ display: flex;
3
+ align-items: center;
4
+ justify-content: center;
5
+ }
@@ -0,0 +1,10 @@
1
+ import * as React from 'react';
2
+
3
+ import { vizConfig } from './config';
4
+ import './noValidDataSign.less';
5
+
6
+ export default () => (
7
+ <div style={{ height: vizConfig.height }} className="no-data-sign">
8
+ No Valid Data is Available for This Metric
9
+ </div>
10
+ );
@@ -0,0 +1,6 @@
1
+ .secondary-ts-x-axis {
2
+ position: absolute;
3
+ g.axis-title.x.axis.bottom text {
4
+ transform: translateY(-8px);
5
+ }
6
+ }
@@ -0,0 +1,58 @@
1
+ import { scaleUtc } from 'd3-scale';
2
+ import * as React from 'react';
3
+ import { Axis } from 'semiotic';
4
+
5
+ import { vizConfig } from './config';
6
+ import CustomAxisTickLabel from './customAxisTickLabel';
7
+ import { IMargin } from './semiotic.service';
8
+ import * as utils from './utils';
9
+
10
+ import './secondaryTSXAxis.less';
11
+
12
+ interface ISecondaryTSXAxisProps {
13
+ margin: IMargin;
14
+ width: number;
15
+ millisSet: number[];
16
+ axisLabel?: string;
17
+ bottomOffset: number;
18
+ }
19
+
20
+ /*
21
+ * Secondary X Axis for Time Series Graph
22
+ * Used when canary and baseline have different start time. We can overlay this axis component
23
+ * on the main graph component
24
+ */
25
+ export default class SecondaryTSXAxis extends React.Component<ISecondaryTSXAxisProps> {
26
+ public render() {
27
+ const { margin, width, millisSet, axisLabel, bottomOffset } = this.props;
28
+
29
+ const { axisTickLineHeight, axisTickLabelHeight, axisLabelHeight } = vizConfig.timeSeries;
30
+
31
+ const extent = [millisSet[0], millisSet[millisSet.length - 1]].map((ms: number) => new Date(ms));
32
+ const totalAxisHeight = axisTickLabelHeight + axisTickLineHeight + (axisLabel ? axisLabelHeight : 0);
33
+ const netWidth = width - margin.left - margin.right;
34
+ const range = [0, netWidth];
35
+ const containerStyle = {
36
+ bottom: bottomOffset,
37
+ };
38
+
39
+ const svgWrapperStyle = {
40
+ transform: `translateX(${margin.left}px)`,
41
+ };
42
+ return (
43
+ <svg className="axis secondary-ts-x-axis" width={width} height={totalAxisHeight} style={containerStyle}>
44
+ <g className="wrapper" style={svgWrapperStyle}>
45
+ <Axis
46
+ className="x axis bottom canary-dual-axis"
47
+ size={[netWidth, axisTickLineHeight]}
48
+ scale={scaleUtc().domain(extent).range(range)}
49
+ orient="bottom"
50
+ label="Canary"
51
+ tickValues={utils.calculateDateTimeTicks(millisSet)}
52
+ tickFormat={(d: number) => <CustomAxisTickLabel millis={d} />}
53
+ />
54
+ </g>
55
+ </svg>
56
+ );
57
+ }
58
+ }
@@ -0,0 +1,32 @@
1
+ import { IMetricSetPairGraphProps } from '../metricSetPairGraph.service';
2
+
3
+ export interface ISemioticChartProps extends IMetricSetPairGraphProps {
4
+ parentWidth: number;
5
+ }
6
+
7
+ export interface IMargin {
8
+ top?: number;
9
+ bottom?: number;
10
+ left?: number;
11
+ right?: number;
12
+ }
13
+
14
+ export interface ITooltip {
15
+ content: JSX.Element;
16
+ x: number;
17
+ y: number;
18
+ }
19
+
20
+ export interface ISummaryStatistics {
21
+ [prop: string]: ISummaryStatisticsValue;
22
+ }
23
+
24
+ export interface ISummaryStatisticsValue {
25
+ value: number;
26
+ label: string;
27
+ }
28
+
29
+ export interface ITimeSeriesDataSets {
30
+ value: number;
31
+ label: string;
32
+ }
@@ -0,0 +1,53 @@
1
+ @import './config.less';
2
+
3
+ .semiotic-graph {
4
+ margin-bottom: 8px;
5
+ .visualization-layer {
6
+ shape-rendering: crispedges;
7
+ .background-graphics {
8
+ .axis-tick-lines {
9
+ stroke: @grid-color;
10
+ opacity: 0.1;
11
+ }
12
+ .axis-title text {
13
+ letter-spacing: 1.5px;
14
+ }
15
+ }
16
+ }
17
+
18
+ .axis {
19
+ font-size: 12px;
20
+ font-weight: 400;
21
+ fill: @axis-color;
22
+ .axis-title.left.y text {
23
+ letter-spacing: 1.5px;
24
+ }
25
+ .x.axis.bottom text.axis-label {
26
+ transform: translateY(-10px);
27
+ }
28
+ .x.axis.bottom text.axis-label:nth-child(2) {
29
+ transform: translateY(5px);
30
+ }
31
+ }
32
+
33
+ .chart-header {
34
+ display: flex;
35
+ align-items: center;
36
+ justify-content: center;
37
+
38
+ .prefix {
39
+ font-weight: 600;
40
+ }
41
+ }
42
+
43
+ .chart-title {
44
+ display: flex;
45
+ justify-content: center;
46
+ flex-direction: row;
47
+ font-size: 12px;
48
+ }
49
+
50
+ .graph-container {
51
+ position: relative;
52
+ }
53
+ }
@@ -0,0 +1,49 @@
1
+ import * as React from 'react';
2
+ import ContainerDimensions from 'react-container-dimensions';
3
+
4
+ import BoxPlot from './boxplot';
5
+ import Histogram from './histogram';
6
+ import { GraphType, IMetricSetPairGraphProps } from '../metricSetPairGraph.service';
7
+ import NoValidDataSign from './noValidDataSign';
8
+ import TimeSeries from './timeSeries';
9
+
10
+ import './semioticGraph.less';
11
+
12
+ export default class SemioticGraph extends React.Component<IMetricSetPairGraphProps> {
13
+ private fetchChart = (parentWidth: number) => {
14
+ const {
15
+ type,
16
+ metricSetPair: {
17
+ values: { control, experiment },
18
+ },
19
+ } = this.props;
20
+ const chartProps = {
21
+ ...this.props,
22
+ parentWidth,
23
+ };
24
+ const filterInvalidValues = (data: number[]) => data.filter((v) => typeof v === 'number');
25
+
26
+ if (filterInvalidValues(control).length === 0 && filterInvalidValues(experiment).length === 0) {
27
+ return <NoValidDataSign />;
28
+ }
29
+
30
+ switch (type) {
31
+ case GraphType.TimeSeries:
32
+ return <TimeSeries {...chartProps} />;
33
+ case GraphType.Histogram:
34
+ return <Histogram {...chartProps} />;
35
+ case GraphType.BoxPlot:
36
+ return <BoxPlot {...chartProps} />;
37
+ default:
38
+ return null;
39
+ }
40
+ };
41
+
42
+ public render() {
43
+ return (
44
+ <div className="semiotic-graph">
45
+ <ContainerDimensions>{({ width }: { width: number }) => this.fetchChart(width)}</ContainerDimensions>
46
+ </div>
47
+ );
48
+ }
49
+ }