@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,157 @@
1
+ import * as Creators from 'kayenta/actions/creators';
2
+ import { ICanaryMetricConfig } from 'kayenta/domain/ICanaryConfig';
3
+ import { DISABLE_EDIT_CONFIG, DisableableReactSelect } from 'kayenta/layout/disableable';
4
+ import FormRow from 'kayenta/layout/formRow';
5
+ import { IUpdateListPayload, List } from 'kayenta/layout/list';
6
+ import RadioChoice from 'kayenta/layout/radioChoice';
7
+ import { ICanaryState } from 'kayenta/reducers';
8
+ import { queryTypeSelector } from 'kayenta/selectors/filterTemplatesSelectors';
9
+ import { get } from 'lodash';
10
+ import * as React from 'react';
11
+ import { connect } from 'react-redux';
12
+ import { Option } from 'react-select';
13
+ import { Action } from 'redux';
14
+ import { createSelector } from 'reselect';
15
+
16
+ import {
17
+ IPrometheusCanaryMetricSetQueryConfig,
18
+ PrometheusQueryType,
19
+ } from './domain/IPrometheusCanaryMetricSetQueryConfig';
20
+ import { ICanaryMetricValidationErrors } from '../../edit/editMetricValidation';
21
+ import PrometheusMetricTypeSelector from './metricTypeSelector';
22
+ import { editingMetricSelector } from '../../selectors';
23
+
24
+ interface IPrometheusMetricConfigurerStateProps {
25
+ editingMetric: ICanaryMetricConfig;
26
+ queryType: PrometheusQueryType;
27
+ validationErrors: ICanaryMetricValidationErrors;
28
+ }
29
+
30
+ interface IPrometheusMetricConfigurerDispatchProps {
31
+ updateLabelBindings: (payload: IUpdateListPayload) => void;
32
+ updateGroupBy: (payload: IUpdateListPayload) => void;
33
+ updateFilterQueryType: (queryType: PrometheusQueryType) => void;
34
+ updatePrometheusMetricQueryField: <T extends keyof IPrometheusCanaryMetricSetQueryConfig>(
35
+ field: keyof IPrometheusCanaryMetricSetQueryConfig,
36
+ value: Option<IPrometheusCanaryMetricSetQueryConfig[T]>,
37
+ ) => void;
38
+ }
39
+
40
+ const RESOURCE_TYPES = ['gce_instance', 'aws_ec2_instance'];
41
+
42
+ const toReactSelectOptions = (values: string[]): Array<Option<string>> =>
43
+ values.map((value) => ({ value, label: value }));
44
+
45
+ /*
46
+ * Component for configuring a Prometheus metric.
47
+ * */
48
+ function PrometheusMetricConfigurer({
49
+ editingMetric,
50
+ queryType,
51
+ updateLabelBindings,
52
+ updateGroupBy,
53
+ updateFilterQueryType,
54
+ updatePrometheusMetricQueryField,
55
+ validationErrors,
56
+ }: IPrometheusMetricConfigurerStateProps & IPrometheusMetricConfigurerDispatchProps) {
57
+ return (
58
+ <>
59
+ <FormRow label="Query Type" helpId="canary.config.prometheus.queryType">
60
+ <RadioChoice
61
+ value={PrometheusQueryType.DEFAULT}
62
+ label="Default"
63
+ name="queryType"
64
+ current={queryType}
65
+ action={() => updateFilterQueryType(PrometheusQueryType.DEFAULT)}
66
+ />
67
+ <RadioChoice
68
+ value={PrometheusQueryType.PROMQL}
69
+ label="PromQL"
70
+ name="queryType"
71
+ current={queryType}
72
+ action={() => updateFilterQueryType(PrometheusQueryType.PROMQL)}
73
+ />
74
+ </FormRow>
75
+ {queryType === PrometheusQueryType.DEFAULT && (
76
+ <>
77
+ <FormRow label="Resource Type" inputOnly={true}>
78
+ <DisableableReactSelect
79
+ value={get(editingMetric, 'query.resourceType')}
80
+ options={toReactSelectOptions(RESOURCE_TYPES)}
81
+ onChange={(option: Option<string>) => updatePrometheusMetricQueryField('resourceType', option)}
82
+ disabledStateKeys={[DISABLE_EDIT_CONFIG]}
83
+ />
84
+ </FormRow>
85
+ <FormRow label="Metric Name" inputOnly={true} error={get(validationErrors, 'metricName.message', null)}>
86
+ <PrometheusMetricTypeSelector
87
+ value={get(editingMetric, 'query.metricName', '')}
88
+ onChange={(option: Option<string>) => updatePrometheusMetricQueryField('metricName', option)}
89
+ />
90
+ </FormRow>
91
+ <FormRow label="Label Bindings">
92
+ <List list={editingMetric.query.labelBindings || []} actionCreator={updateLabelBindings} />
93
+ </FormRow>
94
+ <FormRow label="Group By">
95
+ <List list={editingMetric.query.groupByFields || []} actionCreator={updateGroupBy} />
96
+ </FormRow>
97
+ </>
98
+ )}
99
+ </>
100
+ );
101
+ }
102
+
103
+ function mapStateToProps(state: ICanaryState): IPrometheusMetricConfigurerStateProps {
104
+ return {
105
+ editingMetric: state.selectedConfig.editingMetric,
106
+ queryType: queryTypeSelector(state),
107
+ validationErrors: prometheusMetricValidationSelector(state),
108
+ };
109
+ }
110
+
111
+ function mapDispatchToProps(dispatch: (action: Action & any) => void): IPrometheusMetricConfigurerDispatchProps {
112
+ return {
113
+ updateLabelBindings: (payload) => dispatch(Creators.updatePrometheusLabelBindings(payload)),
114
+ updateGroupBy: (payload) => dispatch(Creators.updatePrometheusGroupBy(payload)),
115
+ updatePrometheusMetricQueryField: (field, option) =>
116
+ dispatch(Creators.updatePrometheusMetricQueryField({ field, value: option && option.value })),
117
+ updateFilterQueryType: (value: PrometheusQueryType) => {
118
+ dispatch(Creators.updatePrometheusMetricQueryField({ field: 'queryType', value }));
119
+ dispatch(Creators.editTemplateCancel()); // clear template editing
120
+ dispatch(Creators.selectTemplate({ name: null })); // deselect template
121
+ dispatch(Creators.editInlineTemplate({ value: '' })); // clear inline template
122
+ },
123
+ };
124
+ }
125
+
126
+ const prometheusMetricValidationSelector = createSelector(editingMetricSelector, validateMetric);
127
+
128
+ /**
129
+ * Validates Prometheus specific fields on the edit metric modal
130
+ */
131
+ function validateMetric(editingMetric: ICanaryMetricConfig): ICanaryMetricValidationErrors {
132
+ const errors: ICanaryMetricValidationErrors = {
133
+ metricName: null,
134
+ };
135
+ return [validatePrometheusMetricName].reduce(
136
+ (reducedErrors, validator) => validator(reducedErrors, editingMetric),
137
+ errors,
138
+ );
139
+ }
140
+
141
+ /**
142
+ * Validates that the user has supplied a Prometheus metric.
143
+ */
144
+ function validatePrometheusMetricName(
145
+ errors: ICanaryMetricValidationErrors,
146
+ editingMetric: ICanaryMetricConfig,
147
+ ): ICanaryMetricValidationErrors {
148
+ const nextErrors = { ...errors };
149
+ const metricName = get(editingMetric, 'query.metricName', '');
150
+ if (!metricName) {
151
+ nextErrors.metricName = { message: 'The Prometheus metric is required.' };
152
+ return nextErrors;
153
+ }
154
+ return nextErrors;
155
+ }
156
+
157
+ export default connect(mapStateToProps, mapDispatchToProps)(PrometheusMetricConfigurer);
@@ -0,0 +1,5 @@
1
+ .prometheus-metric-type-selector-account-hint {
2
+ display: flex;
3
+ align-items: center;
4
+ height: 30px;
5
+ }
@@ -0,0 +1,62 @@
1
+ import { shallow } from 'enzyme';
2
+ import { DisableableReactSelect } from 'kayenta/layout/disableable';
3
+ import * as React from 'react';
4
+
5
+ import { noop } from '@spinnaker/core';
6
+
7
+ import { IPrometheusMetricTypeSelectorProps, PrometheusMetricTypeSelector } from './metricTypeSelector';
8
+
9
+ describe('<PrometheusMetricTypeSelector />', () => {
10
+ let wrapper: any;
11
+ const defaultProps: IPrometheusMetricTypeSelectorProps = {
12
+ accountOptions: [
13
+ {
14
+ label: 'my-first-prometheus-account',
15
+ value: 'my-first-prometheus-account',
16
+ },
17
+ {
18
+ label: 'my-second-prometheus-account',
19
+ value: 'my-second-prometheus-account',
20
+ },
21
+ ],
22
+ load: noop,
23
+ loading: false,
24
+ metricOptions: [],
25
+ onChange: noop,
26
+ value: '',
27
+ };
28
+
29
+ it('renders a typeahead select to search for metrics', () => {
30
+ wrapper = shallow(<PrometheusMetricTypeSelector {...defaultProps} />);
31
+ expect(wrapper.find(DisableableReactSelect).length).toEqual(1);
32
+ expect(wrapper.find(DisableableReactSelect).props().placeholder).toEqual(
33
+ 'Enter at least three characters to search.',
34
+ );
35
+ });
36
+
37
+ it('displays which account will populate the search when >1 account is configured, defaulting to the first account alphabetically', () => {
38
+ wrapper = shallow(<PrometheusMetricTypeSelector {...defaultProps} />);
39
+ expect(wrapper.find('.prometheus-metric-type-selector-account-hint span').at(0).text()).toEqual(
40
+ 'Metric search is currently populating from my-first-prometheus-account.',
41
+ );
42
+ });
43
+
44
+ it('allows the user to switch which account populates the search when >1 account is configured', () => {
45
+ wrapper = shallow(<PrometheusMetricTypeSelector {...defaultProps} />);
46
+ expect(wrapper.find('.btn').text()).toEqual('Switch Account');
47
+ wrapper.find('.btn').simulate('click');
48
+ expect(wrapper.state('showAccountDropdown')).toEqual(true);
49
+ expect(wrapper.find(DisableableReactSelect).length).toEqual(2);
50
+ });
51
+
52
+ it('does not display account selection hint when there is only one account configured', () => {
53
+ wrapper = shallow(
54
+ <PrometheusMetricTypeSelector
55
+ {...defaultProps}
56
+ accountOptions={[{ label: 'my-only-prometheus-account', value: 'my-only-prometheus-account' }]}
57
+ />,
58
+ );
59
+ expect(wrapper.find('.prometheus-metric-type-selector-account-hint').length).toEqual(0);
60
+ expect(wrapper.find('.btn').length).toEqual(0);
61
+ });
62
+ });
@@ -0,0 +1,144 @@
1
+ import * as Creators from 'kayenta/actions/creators';
2
+ import { KayentaAccountType } from 'kayenta/domain';
3
+ import { DISABLE_EDIT_CONFIG, DisableableReactSelect } from 'kayenta/layout/disableable';
4
+ import { ICanaryState } from 'kayenta/reducers';
5
+ import { AsyncRequestState } from 'kayenta/reducers/asyncRequest';
6
+ import { chain, get } from 'lodash';
7
+ import * as React from 'react';
8
+ import { connect } from 'react-redux';
9
+ import { Option } from 'react-select';
10
+ import { Dispatch } from 'redux';
11
+ import { createSelector } from 'reselect';
12
+
13
+ import { IPrometheusMetricDescriptor } from './domain/IPrometheusMetricDescriptor';
14
+
15
+ import './metricTypeSelector.less';
16
+
17
+ interface IPrometheusMetricTypeSelectorDispatchProps {
18
+ load: (filter: string, metricsAccountName: string) => void;
19
+ }
20
+
21
+ interface IPrometheusMetricTypeSelectorStateProps {
22
+ accountOptions: Array<Option<string>>;
23
+ loading: boolean;
24
+ metricOptions: Array<Option<string>>;
25
+ }
26
+
27
+ interface IPrometheusMetricTypeSelectorOwnProps {
28
+ onChange: (option: Option<string>) => void;
29
+ value: string;
30
+ }
31
+
32
+ interface IPrometheusMetricTypeSelectorState {
33
+ selectedAccount: string;
34
+ showAccountDropdown: boolean;
35
+ }
36
+
37
+ export type IPrometheusMetricTypeSelectorProps = IPrometheusMetricTypeSelectorDispatchProps &
38
+ IPrometheusMetricTypeSelectorStateProps &
39
+ IPrometheusMetricTypeSelectorOwnProps;
40
+
41
+ export class PrometheusMetricTypeSelector extends React.Component<
42
+ IPrometheusMetricTypeSelectorProps,
43
+ IPrometheusMetricTypeSelectorState
44
+ > {
45
+ public constructor(props: IPrometheusMetricTypeSelectorProps) {
46
+ super(props);
47
+ this.state = {
48
+ showAccountDropdown: false,
49
+ selectedAccount: get(props, ['accountOptions', 0, 'value']),
50
+ };
51
+ }
52
+
53
+ public render() {
54
+ const { accountOptions, load, loading, metricOptions, onChange, value } = this.props;
55
+
56
+ const metricOptionsWithSelected =
57
+ value && metricOptions.every((o) => o.value !== value)
58
+ ? metricOptions.concat({ label: value, value })
59
+ : metricOptions;
60
+
61
+ return (
62
+ <>
63
+ <DisableableReactSelect
64
+ isLoading={loading}
65
+ options={metricOptionsWithSelected}
66
+ onChange={onChange}
67
+ value={value}
68
+ placeholder={'Enter at least three characters to search.'}
69
+ onInputChange={(input) => {
70
+ load(input, this.state.selectedAccount);
71
+ return input;
72
+ }}
73
+ disabledStateKeys={[DISABLE_EDIT_CONFIG]}
74
+ />
75
+ {accountOptions.length > 1 && (
76
+ <div className="prometheus-metric-type-selector-account-hint">
77
+ <span>Metric search is currently populating from {this.state.selectedAccount}.</span>
78
+ <span className="btn btn-link" onClick={this.showAccountDropdown}>
79
+ {!this.state.showAccountDropdown && 'Switch Account'}
80
+ </span>
81
+ </div>
82
+ )}
83
+ {this.state.showAccountDropdown && (
84
+ <DisableableReactSelect
85
+ clearable={false}
86
+ options={accountOptions}
87
+ onChange={this.selectAccount}
88
+ value={this.state.selectedAccount}
89
+ disabledStateKeys={[DISABLE_EDIT_CONFIG]}
90
+ />
91
+ )}
92
+ </>
93
+ );
94
+ }
95
+
96
+ private showAccountDropdown = (): void => {
97
+ this.setState({
98
+ showAccountDropdown: true,
99
+ });
100
+ };
101
+
102
+ private selectAccount = (option: Option<string>): void => {
103
+ this.setState({
104
+ selectedAccount: option.value,
105
+ showAccountDropdown: false,
106
+ });
107
+ };
108
+ }
109
+
110
+ const accountOptionsSelector = createSelector(
111
+ (state: ICanaryState) => state.data.kayentaAccounts.data,
112
+ (accounts): Array<Option<string>> => {
113
+ return chain(accounts)
114
+ .filter((a) => a.supportedTypes.includes(KayentaAccountType.MetricsStore) && a.type === 'prometheus')
115
+ .sortBy((a) => a.name)
116
+ .map((a) => ({ label: a.name, value: a.name }))
117
+ .value();
118
+ },
119
+ );
120
+
121
+ const metricOptionsSelector = createSelector(
122
+ (state: ICanaryState) => state.data.metricsServiceMetadata.data,
123
+ (descriptors: IPrometheusMetricDescriptor[]): Array<Option<string>> =>
124
+ descriptors.map((d) => ({ label: d.name, value: d.name })),
125
+ );
126
+
127
+ const mapStateToProps = (state: ICanaryState, ownProps: IPrometheusMetricTypeSelectorOwnProps) => {
128
+ return {
129
+ accountOptions: accountOptionsSelector(state),
130
+ metricOptions: metricOptionsSelector(state),
131
+ loading: state.data.metricsServiceMetadata.load === AsyncRequestState.Requesting,
132
+ ...ownProps,
133
+ };
134
+ };
135
+
136
+ const mapDispatchToProps = (dispatch: Dispatch<ICanaryState>) => {
137
+ return {
138
+ load: (filter: string, metricsAccountName: string) => {
139
+ dispatch(Creators.updatePrometheusMetricDescriptorFilter({ filter, metricsAccountName }));
140
+ },
141
+ };
142
+ };
143
+
144
+ export default connect(mapStateToProps, mapDispatchToProps)(PrometheusMetricTypeSelector);
@@ -0,0 +1,61 @@
1
+ import { ICanaryMetricConfig } from '../../domain';
2
+ import { PrometheusQueryType } from './domain/IPrometheusCanaryMetricSetQueryConfig';
3
+ import { appendPromQLPrefix, getPrometheusQueryType, isQueryPromQL, removePromQLPrefix } from './queryTypeSelectors';
4
+
5
+ describe('Prometheus query type selectors', () => {
6
+ describe('isQueryPromQL', () => {
7
+ it('identifies PromQL queries by prefix', () => {
8
+ expect(isQueryPromQL('PromQL:count(prometheus_target_interval_length_seconds)')).toEqual(true);
9
+ expect(isQueryPromQL('metadata.user_labels."app"="${location}"')).toEqual(false);
10
+ });
11
+ });
12
+ describe('removePromQLPrefix', () => {
13
+ it('removes PromQL prefix from query', () => {
14
+ expect(removePromQLPrefix('PromQL:count(prometheus_target_interval_length_seconds)')).toEqual(
15
+ 'count(prometheus_target_interval_length_seconds)',
16
+ );
17
+ });
18
+ it('does not modify queries not prefixed by the PromQL prefix', () => {
19
+ expect(removePromQLPrefix('Not a PromQL template')).toEqual('Not a PromQL template');
20
+ });
21
+ });
22
+ describe('appendPromQLPrefix', () => {
23
+ it('appends the PromQL prefix to the input', () => {
24
+ expect(appendPromQLPrefix('count(prometheus_target_interval_length_seconds)')).toEqual(
25
+ 'PromQL:count(prometheus_target_interval_length_seconds)',
26
+ );
27
+ });
28
+ });
29
+ describe('getPrometheusQueryType', () => {
30
+ let editingMetric: ICanaryMetricConfig;
31
+ beforeEach(() => {
32
+ editingMetric = {
33
+ id: '#0',
34
+ name: 'my metric',
35
+ groups: ['group-1'],
36
+ analysisConfigurations: null,
37
+ scopeName: 'default',
38
+ query: {
39
+ customFilterTemplate: 'myDefaultTemplate',
40
+ queryType: PrometheusQueryType.DEFAULT,
41
+ },
42
+ };
43
+ });
44
+ it('reads query type from metric state if set', () => {
45
+ expect(getPrometheusQueryType(editingMetric)).toEqual(PrometheusQueryType.DEFAULT);
46
+ editingMetric.query.queryType = PrometheusQueryType.PROMQL;
47
+ expect(getPrometheusQueryType(editingMetric)).toEqual(PrometheusQueryType.PROMQL);
48
+ });
49
+ it('infers query type from custom inline template if query type not set on state', () => {
50
+ editingMetric.query.queryType = null;
51
+ expect(getPrometheusQueryType(editingMetric)).toEqual(PrometheusQueryType.DEFAULT);
52
+ editingMetric.query.customInlineTemplate = 'PromQL:count(prometheus_target_interval_length_seconds)';
53
+ expect(getPrometheusQueryType(editingMetric)).toEqual(PrometheusQueryType.PROMQL);
54
+ });
55
+ it('defaults to PrometheusQueryType.DEFAULT if query type not set on state and no inline template added', () => {
56
+ editingMetric.query.queryType = null;
57
+ editingMetric.query.customInlineTemplate = null;
58
+ expect(getPrometheusQueryType(editingMetric)).toEqual(PrometheusQueryType.DEFAULT);
59
+ });
60
+ });
61
+ });
@@ -0,0 +1,38 @@
1
+ import { ICanaryMetricConfig } from '../../domain';
2
+ import { PrometheusQueryType } from './domain/IPrometheusCanaryMetricSetQueryConfig';
3
+ import { ITemplateTransformFunctions } from '../../selectors/filterTemplatesSelectors';
4
+
5
+ const PROMQL_PREFIX = 'PromQL:';
6
+
7
+ export function isQueryPromQL(template: string): boolean {
8
+ return template != null && template.startsWith(PROMQL_PREFIX);
9
+ }
10
+
11
+ export function removePromQLPrefix(template: string): string {
12
+ if (isQueryPromQL(template)) {
13
+ return template.substring(PROMQL_PREFIX.length);
14
+ }
15
+ return template;
16
+ }
17
+
18
+ export function appendPromQLPrefix(template: string): string {
19
+ return `${PROMQL_PREFIX}${template}`;
20
+ }
21
+
22
+ export const getPrometheusQueryType = (editingMetric: ICanaryMetricConfig) => {
23
+ if (editingMetric.query.queryType) {
24
+ return editingMetric.query.queryType;
25
+ }
26
+ const inlineTemplate = editingMetric.query.customInlineTemplate;
27
+ if (isQueryPromQL(inlineTemplate)) {
28
+ return PrometheusQueryType.PROMQL;
29
+ }
30
+ return PrometheusQueryType.DEFAULT;
31
+ };
32
+
33
+ export const prometheusQueryTypeToTransformFunction: { [queryType: string]: ITemplateTransformFunctions } = {
34
+ [PrometheusQueryType.PROMQL]: {
35
+ fromValue: removePromQLPrefix,
36
+ toValue: appendPromQLPrefix,
37
+ },
38
+ };
@@ -0,0 +1,7 @@
1
+ import { ICanaryMetricSetQueryConfig } from 'kayenta/domain';
2
+
3
+ export interface ISignalFxCanaryMetricSetQueryConfig extends ICanaryMetricSetQueryConfig {
4
+ metricName: string;
5
+ aggregationMethod: string;
6
+ queryPairs: [{ key: string; value: string }];
7
+ }
@@ -0,0 +1,8 @@
1
+ import SignalFxMetricConfigurer, { queryFinder } from './metricConfigurer';
2
+ import metricStoreConfigStore from '../metricStoreConfig.service';
3
+
4
+ metricStoreConfigStore.register({
5
+ name: 'signalfx',
6
+ metricConfigurer: SignalFxMetricConfigurer,
7
+ queryFinder,
8
+ });
@@ -0,0 +1,10 @@
1
+ .signalfx-query-pairs {
2
+ padding: 10px 10px 0;
3
+ border-radius: 4px;
4
+ border: 1px solid #ccc;
5
+
6
+ .add-new {
7
+ margin-bottom: 10px;
8
+ margin-top: 5px;
9
+ }
10
+ }
@@ -0,0 +1,187 @@
1
+ import autoBindMethods from 'class-autobind-decorator';
2
+ import * as Creators from 'kayenta/actions/creators';
3
+ import { ICanaryMetricConfig } from 'kayenta/domain';
4
+ import { DISABLE_EDIT_CONFIG, DisableableInput } from 'kayenta/layout/disableable';
5
+ import FormRow from 'kayenta/layout/formRow';
6
+ import { ICanaryState } from 'kayenta/reducers';
7
+ import { get } from 'lodash';
8
+ import * as React from 'react';
9
+ import { connect } from 'react-redux';
10
+ import { Action } from 'redux';
11
+ import { createSelector } from 'reselect';
12
+
13
+ import { ICanaryMetricValidationErrors, MetricValidatorFunction } from '../../edit/editMetricValidation';
14
+ import KeyValueList, { IKeyValuePair, IUpdateKeyValueListPayload } from '../../layout/keyValueList';
15
+ import { editingMetricSelector } from '../../selectors';
16
+
17
+ import './metricConfigurer.less';
18
+
19
+ interface ISignalFxMetricConfigurerStateProps {
20
+ editingMetric: ICanaryMetricConfig;
21
+ validationErrors: ICanaryMetricValidationErrors;
22
+ }
23
+
24
+ interface ISignalFxMetricConfigurerDispatchProps {
25
+ updateMetricName: (name: string) => void;
26
+ updateAggregationMethod: (method: string) => void;
27
+ updateQueryPairs: (payload: IUpdateKeyValueListPayload) => void;
28
+ }
29
+
30
+ type SignalFxMetricConfigurerProps = ISignalFxMetricConfigurerStateProps & ISignalFxMetricConfigurerDispatchProps;
31
+
32
+ export const queryFinder = (metric: ICanaryMetricConfig) => get(metric, 'query.metricName', '');
33
+ const getSignalFxMetric = queryFinder;
34
+ const getQueryPairs = (metric: ICanaryMetricConfig) => get(metric, 'query.queryPairs', []) as IKeyValuePair[];
35
+ const getAggregationMethod = (metric: ICanaryMetricConfig) => get(metric, 'query.aggregationMethod', '');
36
+
37
+ @autoBindMethods
38
+ class SignalFxMetricConfigurer extends React.Component<SignalFxMetricConfigurerProps> {
39
+ public onMetricNameChange(e: React.ChangeEvent<HTMLInputElement>) {
40
+ this.props.updateMetricName(e.target.value);
41
+ }
42
+
43
+ public onAggregationMethodChange(e: React.ChangeEvent<HTMLInputElement>) {
44
+ this.props.updateAggregationMethod(e.target.value);
45
+ }
46
+
47
+ public render() {
48
+ const { editingMetric, updateQueryPairs, validationErrors } = this.props;
49
+
50
+ return (
51
+ <section>
52
+ <FormRow label="SignalFx Metric" error={get(validationErrors, 'signalFxMetric.message', null)} inputOnly={true}>
53
+ <DisableableInput
54
+ type="text"
55
+ value={getSignalFxMetric(editingMetric)}
56
+ onChange={this.onMetricNameChange}
57
+ disabledStateKeys={[DISABLE_EDIT_CONFIG]}
58
+ />
59
+ </FormRow>
60
+ <FormRow
61
+ label="Aggregation Method"
62
+ inputOnly={true}
63
+ helpId="canary.config.signalFx.aggregationMethod"
64
+ error={get(validationErrors, 'aggregationMethod.message', null)}
65
+ >
66
+ <DisableableInput
67
+ type="text"
68
+ value={getAggregationMethod(editingMetric)}
69
+ onChange={this.onAggregationMethodChange}
70
+ disabledStateKeys={[DISABLE_EDIT_CONFIG]}
71
+ />
72
+ </FormRow>
73
+ <FormRow
74
+ label="Query Pairs"
75
+ helpId="canary.config.signalFx.queryPairs"
76
+ error={get(validationErrors, 'queryPairs.message', null)}
77
+ >
78
+ <KeyValueList
79
+ className="signalfx-query-pairs"
80
+ list={getQueryPairs(editingMetric)}
81
+ actionCreator={updateQueryPairs}
82
+ />
83
+ </FormRow>
84
+ </section>
85
+ );
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Validates SignalFx specific fields on the edit metric modal
91
+ */
92
+ export function validateMetric(editingMetric: ICanaryMetricConfig): ICanaryMetricValidationErrors {
93
+ const errors: ICanaryMetricValidationErrors = {};
94
+
95
+ const validators: MetricValidatorFunction[] = [...getSignalFxValidators(editingMetric)];
96
+
97
+ return validators.reduce((reducedErrors, validator) => validator(reducedErrors, editingMetric), errors);
98
+ }
99
+
100
+ /**
101
+ * returns the list of validators for the SignalFx edit metric form.
102
+ */
103
+ export function getSignalFxValidators(editingMetric: ICanaryMetricConfig): MetricValidatorFunction[] {
104
+ if (!editingMetric || !editingMetric.query) {
105
+ return [];
106
+ }
107
+ return [validateSignalFxMetricName, validateAggregationMethod, validateQueryPairs];
108
+ }
109
+
110
+ /**
111
+ * Validates that the user has supplied a SignalFx metric.
112
+ */
113
+ function validateSignalFxMetricName(
114
+ errors: ICanaryMetricValidationErrors,
115
+ editingMetric: ICanaryMetricConfig,
116
+ ): ICanaryMetricValidationErrors {
117
+ const nextErrors = { ...errors };
118
+
119
+ const signalFxMetric = getSignalFxMetric(editingMetric);
120
+
121
+ if (!signalFxMetric) {
122
+ nextErrors.signalFxMetric = { message: 'The SignalFx metric is required.' };
123
+ }
124
+
125
+ return nextErrors;
126
+ }
127
+
128
+ /**
129
+ * Validates that the user has supplied an aggregation method.
130
+ */
131
+ function validateAggregationMethod(
132
+ errors: ICanaryMetricValidationErrors,
133
+ editingMetric: ICanaryMetricConfig,
134
+ ): ICanaryMetricValidationErrors {
135
+ const nextErrors = { ...errors };
136
+
137
+ const aggregationMethod = getAggregationMethod(editingMetric);
138
+
139
+ if (!aggregationMethod) {
140
+ nextErrors.aggregationMethod = { message: 'The SignalFx SignalFlow stream aggregation method is required.' };
141
+ }
142
+
143
+ return nextErrors;
144
+ }
145
+
146
+ /**
147
+ * Validates that if the user has supplied query pairs that all key value combos contain values.
148
+ */
149
+ function validateQueryPairs(
150
+ errors: ICanaryMetricValidationErrors,
151
+ editingMetric: ICanaryMetricConfig,
152
+ ): ICanaryMetricValidationErrors {
153
+ const nextErrors = { ...errors };
154
+
155
+ const queryPairs: IKeyValuePair[] = getQueryPairs(editingMetric);
156
+
157
+ queryPairs.forEach((qp) => {
158
+ if (!qp.key || !qp.value) {
159
+ nextErrors.queryPairs = { message: 'All query pairs must contain a non-blank key and value.' };
160
+ }
161
+ });
162
+
163
+ return nextErrors;
164
+ }
165
+
166
+ const sfxEditingMetricValidationErrorsSelector = createSelector(editingMetricSelector, validateMetric);
167
+
168
+ function mapStateToProps(state: ICanaryState): ISignalFxMetricConfigurerStateProps {
169
+ return {
170
+ editingMetric: state.selectedConfig.editingMetric,
171
+ validationErrors: sfxEditingMetricValidationErrorsSelector(state),
172
+ };
173
+ }
174
+
175
+ function mapDispatchToProps(dispatch: (action: Action & any) => void): ISignalFxMetricConfigurerDispatchProps {
176
+ return {
177
+ updateMetricName: (metricName: string): void => {
178
+ dispatch(Creators.updateSignalFxMetricName({ metricName }));
179
+ },
180
+ updateAggregationMethod: (aggregationMethod: string): void => {
181
+ dispatch(Creators.updateSignalFxAggregationMethod({ aggregationMethod }));
182
+ },
183
+ updateQueryPairs: (payload) => dispatch(Creators.updateSignalFxQueryPairs(payload)),
184
+ };
185
+ }
186
+
187
+ export default connect(mapStateToProps, mapDispatchToProps)(SignalFxMetricConfigurer);