@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.
- package/.editorconfig +9 -0
- package/.eslintrc.js +1 -0
- package/.huskyrc +5 -0
- package/.lintstagedrc.json +4 -0
- package/.prettierignore +4 -0
- package/.prettierrc.js +1 -0
- package/LICENSE.txt +203 -0
- package/README.md +81 -0
- package/__mocks__/styleMock.js +1 -0
- package/__mocks__/version.json +4 -0
- package/babel.config.js +3 -0
- package/build.gradle +67 -0
- package/build_scripts/checkLicenses.js +79 -0
- package/gradle.properties +0 -0
- package/jest.config.js +204 -0
- package/jest.setup.js +5 -0
- package/package.json +166 -0
- package/rollup-plugin-angularjs-template-loader.js +82 -0
- package/rollup.config.js +30 -0
- package/src/index.ts +2 -0
- package/src/kayenta/actions/creators.ts +163 -0
- package/src/kayenta/actions/index.ts +98 -0
- package/src/kayenta/canary.dataSource.bridge.ts +53 -0
- package/src/kayenta/canary.dataSource.stub.ts +64 -0
- package/src/kayenta/canary.help.ts +136 -0
- package/src/kayenta/canary.less +168 -0
- package/src/kayenta/canary.settings.ts +26 -0
- package/src/kayenta/canary.tsx +67 -0
- package/src/kayenta/components/canaryScore.component.less +77 -0
- package/src/kayenta/components/canaryScore.component.ts +12 -0
- package/src/kayenta/components/canaryScore.tsx +63 -0
- package/src/kayenta/components/canaryScores.component.ts +20 -0
- package/src/kayenta/components/canaryScores.less +22 -0
- package/src/kayenta/components/canaryScores.tsx +163 -0
- package/src/kayenta/components/loadStates.tsx +52 -0
- package/src/kayenta/domain/ICanaryConfig.ts +57 -0
- package/src/kayenta/domain/ICanaryConfigSummary.ts +7 -0
- package/src/kayenta/domain/ICanaryConfigUpdateResponse.ts +3 -0
- package/src/kayenta/domain/ICanaryExecutionStatusResult.ts +72 -0
- package/src/kayenta/domain/ICanaryJudgeResult.ts +51 -0
- package/src/kayenta/domain/ICanaryJudgeResultSummary.ts +5 -0
- package/src/kayenta/domain/ICanaryScoreThresholds.ts +4 -0
- package/src/kayenta/domain/IJudge.ts +5 -0
- package/src/kayenta/domain/IKayentaAccount.ts +14 -0
- package/src/kayenta/domain/IKayentaStageConfig.ts +58 -0
- package/src/kayenta/domain/IMetricSetPair.ts +17 -0
- package/src/kayenta/domain/IMetricsServiceMetadata.ts +2 -0
- package/src/kayenta/domain/ISetupCanaryStage.ts +11 -0
- package/src/kayenta/domain/MetricClassificationLabel.ts +8 -0
- package/src/kayenta/domain/ScoreClassificationLabel.ts +7 -0
- package/src/kayenta/domain/index.ts +15 -0
- package/src/kayenta/edit/changeMetricGroupModal.tsx +107 -0
- package/src/kayenta/edit/configDetail.tsx +31 -0
- package/src/kayenta/edit/configDetailActionButtons.tsx +24 -0
- package/src/kayenta/edit/configDetailHeader.tsx +104 -0
- package/src/kayenta/edit/configDetailLoadStates.tsx +36 -0
- package/src/kayenta/edit/configDetailLoader.tsx +60 -0
- package/src/kayenta/edit/configJson.less +42 -0
- package/src/kayenta/edit/configJsonModal.tsx +158 -0
- package/src/kayenta/edit/configList.less +7 -0
- package/src/kayenta/edit/configList.tsx +57 -0
- package/src/kayenta/edit/copyConfigButton.tsx +34 -0
- package/src/kayenta/edit/createConfigButton.tsx +34 -0
- package/src/kayenta/edit/deleteModal.tsx +87 -0
- package/src/kayenta/edit/edit.tsx +24 -0
- package/src/kayenta/edit/editMetricEffectSizes.tsx +186 -0
- package/src/kayenta/edit/editMetricModal.less +9 -0
- package/src/kayenta/edit/editMetricModal.spec.tsx +129 -0
- package/src/kayenta/edit/editMetricModal.tsx +294 -0
- package/src/kayenta/edit/editMetricValidation.spec.ts +63 -0
- package/src/kayenta/edit/editMetricValidation.ts +50 -0
- package/src/kayenta/edit/filterTemplateSelector.less +15 -0
- package/src/kayenta/edit/filterTemplateSelector.spec.tsx +106 -0
- package/src/kayenta/edit/filterTemplateSelector.tsx +194 -0
- package/src/kayenta/edit/filterTemplatesValidation.spec.ts +108 -0
- package/src/kayenta/edit/filterTemplatesValidation.ts +95 -0
- package/src/kayenta/edit/footer.less +30 -0
- package/src/kayenta/edit/footer.tsx +12 -0
- package/src/kayenta/edit/groupName.tsx +80 -0
- package/src/kayenta/edit/groupTabs.tsx +106 -0
- package/src/kayenta/edit/groupWeight.tsx +80 -0
- package/src/kayenta/edit/groupWeights.tsx +38 -0
- package/src/kayenta/edit/inlineTemplateEditor.spec.tsx +42 -0
- package/src/kayenta/edit/inlineTemplateEditor.tsx +61 -0
- package/src/kayenta/edit/judgeSelect.tsx +82 -0
- package/src/kayenta/edit/metricConfigurerDelegator.tsx +30 -0
- package/src/kayenta/edit/metricList.less +21 -0
- package/src/kayenta/edit/metricList.tsx +215 -0
- package/src/kayenta/edit/metricStoreSelector.tsx +66 -0
- package/src/kayenta/edit/nameAndDescription.tsx +90 -0
- package/src/kayenta/edit/openConfigJsonModalButton.tsx +33 -0
- package/src/kayenta/edit/openDeleteModalButton.tsx +50 -0
- package/src/kayenta/edit/ownedBy.tsx +34 -0
- package/src/kayenta/edit/save.tsx +19 -0
- package/src/kayenta/edit/saveConfigButton.tsx +65 -0
- package/src/kayenta/edit/saveConfigError.tsx +59 -0
- package/src/kayenta/edit/scoring.tsx +35 -0
- package/src/kayenta/edit/selectConfig.tsx +10 -0
- package/src/kayenta/edit/validationErrors.tsx +39 -0
- package/src/kayenta/index.ts +6 -0
- package/src/kayenta/layout/addNewButton.tsx +20 -0
- package/src/kayenta/layout/centeredDetail.tsx +13 -0
- package/src/kayenta/layout/deleteButton.tsx +11 -0
- package/src/kayenta/layout/disableable.tsx +87 -0
- package/src/kayenta/layout/formList.tsx +26 -0
- package/src/kayenta/layout/formRow.tsx +36 -0
- package/src/kayenta/layout/formattedDate.tsx +14 -0
- package/src/kayenta/layout/index.ts +2 -0
- package/src/kayenta/layout/keyValueList.less +20 -0
- package/src/kayenta/layout/keyValueList.tsx +114 -0
- package/src/kayenta/layout/list.less +9 -0
- package/src/kayenta/layout/list.spec.tsx +83 -0
- package/src/kayenta/layout/list.tsx +73 -0
- package/src/kayenta/layout/listDetail.tsx +33 -0
- package/src/kayenta/layout/radioChoice.tsx +29 -0
- package/src/kayenta/layout/styleguide.tsx +16 -0
- package/src/kayenta/layout/table/index.ts +5 -0
- package/src/kayenta/layout/table/nativeTable.tsx +51 -0
- package/src/kayenta/layout/table/nativeTableHeader.tsx +26 -0
- package/src/kayenta/layout/table/table.tsx +56 -0
- package/src/kayenta/layout/table/tableColumn.ts +7 -0
- package/src/kayenta/layout/table/tableHeader.tsx +23 -0
- package/src/kayenta/layout/tabs.tsx +26 -0
- package/src/kayenta/layout/titledSection.less +16 -0
- package/src/kayenta/layout/titledSection.tsx +20 -0
- package/src/kayenta/layout/titledSubsection.less +11 -0
- package/src/kayenta/layout/titledSubsection.tsx +22 -0
- package/src/kayenta/manualAnalysis/ManualAnalysisModal.tsx +716 -0
- package/src/kayenta/metricStore/atlas/atlasMetricConfigurer.tsx +130 -0
- package/src/kayenta/metricStore/atlas/index.ts +8 -0
- package/src/kayenta/metricStore/datadog/domain/IDatadogMetricDescriptor.ts +5 -0
- package/src/kayenta/metricStore/datadog/index.ts +9 -0
- package/src/kayenta/metricStore/datadog/metricConfigurer.tsx +90 -0
- package/src/kayenta/metricStore/datadog/metricTypeSelector.spec.tsx +59 -0
- package/src/kayenta/metricStore/datadog/metricTypeSelector.tsx +73 -0
- package/src/kayenta/metricStore/graphite/domain/IGraphiteMetricDescriptor.ts +5 -0
- package/src/kayenta/metricStore/graphite/index.ts +8 -0
- package/src/kayenta/metricStore/graphite/metricConfigurer.tsx +54 -0
- package/src/kayenta/metricStore/graphite/metricTypeSelector.tsx +80 -0
- package/src/kayenta/metricStore/graphite/typeahead.less +3 -0
- package/src/kayenta/metricStore/index.ts +8 -0
- package/src/kayenta/metricStore/metricStoreConfig.service.ts +12 -0
- package/src/kayenta/metricStore/newrelic/domain/INewRelicMetricDescriptor.ts +5 -0
- package/src/kayenta/metricStore/newrelic/index.ts +8 -0
- package/src/kayenta/metricStore/newrelic/metricConfigurer.tsx +58 -0
- package/src/kayenta/metricStore/prometheus/domain/IPrometheusCanaryMetricSetQueryConfig.ts +14 -0
- package/src/kayenta/metricStore/prometheus/domain/IPrometheusMetricDescriptor.ts +5 -0
- package/src/kayenta/metricStore/prometheus/index.ts +12 -0
- package/src/kayenta/metricStore/prometheus/metricConfigurer.tsx +157 -0
- package/src/kayenta/metricStore/prometheus/metricTypeSelector.less +5 -0
- package/src/kayenta/metricStore/prometheus/metricTypeSelector.spec.tsx +62 -0
- package/src/kayenta/metricStore/prometheus/metricTypeSelector.tsx +144 -0
- package/src/kayenta/metricStore/prometheus/queryTypeSelectors.spec.ts +61 -0
- package/src/kayenta/metricStore/prometheus/queryTypeSelectors.ts +38 -0
- package/src/kayenta/metricStore/signalfx/domain/ISignalFxCanaryMetricSetQueryConfig.ts +7 -0
- package/src/kayenta/metricStore/signalfx/index.ts +8 -0
- package/src/kayenta/metricStore/signalfx/metricConfigurer.less +10 -0
- package/src/kayenta/metricStore/signalfx/metricConfigurer.tsx +187 -0
- package/src/kayenta/metricStore/stackdriver/domain/IStackdriverCanaryMetricSetQueryConfig.ts +9 -0
- package/src/kayenta/metricStore/stackdriver/domain/IStackdriverMetricDescriptor.ts +17 -0
- package/src/kayenta/metricStore/stackdriver/index.ts +12 -0
- package/src/kayenta/metricStore/stackdriver/metricConfigurer.tsx +144 -0
- package/src/kayenta/metricStore/stackdriver/metricTypeSelector.spec.tsx +92 -0
- package/src/kayenta/metricStore/stackdriver/metricTypeSelector.tsx +113 -0
- package/src/kayenta/middleware/actionInterceptor.ts +29 -0
- package/src/kayenta/middleware/asyncDispatch.ts +37 -0
- package/src/kayenta/middleware/epics.ts +211 -0
- package/src/kayenta/middleware/index.ts +3 -0
- package/src/kayenta/navigation/canary.states.stub.ts +28 -0
- package/src/kayenta/navigation/canary.states.ts +182 -0
- package/src/kayenta/reducers/app.ts +56 -0
- package/src/kayenta/reducers/asyncRequest.ts +5 -0
- package/src/kayenta/reducers/data.ts +169 -0
- package/src/kayenta/reducers/editingTemplate.ts +54 -0
- package/src/kayenta/reducers/group.ts +82 -0
- package/src/kayenta/reducers/index.ts +245 -0
- package/src/kayenta/reducers/prometheusMetricConfig.spec.ts +33 -0
- package/src/kayenta/reducers/prometheusMetricConfig.ts +56 -0
- package/src/kayenta/reducers/selectedConfig.spec.ts +190 -0
- package/src/kayenta/reducers/selectedConfig.ts +566 -0
- package/src/kayenta/reducers/selectedRun.ts +101 -0
- package/src/kayenta/reducers/signalFxMetricConfig.ts +36 -0
- package/src/kayenta/reducers/stackdriverMetricConfig.spec.ts +33 -0
- package/src/kayenta/reducers/stackdriverMetricConfig.ts +41 -0
- package/src/kayenta/reducers/templates.spec.ts +192 -0
- package/src/kayenta/reducers/validators.ts +118 -0
- package/src/kayenta/report/detail/allMetricResultsHeader.tsx +32 -0
- package/src/kayenta/report/detail/clickableHeader.tsx +21 -0
- package/src/kayenta/report/detail/colors.ts +47 -0
- package/src/kayenta/report/detail/detail.less +16 -0
- package/src/kayenta/report/detail/detail.tsx +48 -0
- package/src/kayenta/report/detail/detailLoader.tsx +55 -0
- package/src/kayenta/report/detail/graph/graph.tsx +37 -0
- package/src/kayenta/report/detail/graph/metricSetPairGraph.service.ts +35 -0
- package/src/kayenta/report/detail/graph/semiotic/boxplot.less +45 -0
- package/src/kayenta/report/detail/graph/semiotic/boxplot.tsx +283 -0
- package/src/kayenta/report/detail/graph/semiotic/chartHeader.tsx +19 -0
- package/src/kayenta/report/detail/graph/semiotic/chartLegend.less +26 -0
- package/src/kayenta/report/detail/graph/semiotic/chartLegend.tsx +42 -0
- package/src/kayenta/report/detail/graph/semiotic/circleIcon.tsx +16 -0
- package/src/kayenta/report/detail/graph/semiotic/config.less +5 -0
- package/src/kayenta/report/detail/graph/semiotic/config.ts +38 -0
- package/src/kayenta/report/detail/graph/semiotic/customAxisTickLabel.tsx +17 -0
- package/src/kayenta/report/detail/graph/semiotic/declarations/labella.d.ts +16 -0
- package/src/kayenta/report/detail/graph/semiotic/declarations/react-container-dimensions.d.ts +3 -0
- package/src/kayenta/report/detail/graph/semiotic/declarations/semiotic.d.ts +160 -0
- package/src/kayenta/report/detail/graph/semiotic/differenceArea.less +17 -0
- package/src/kayenta/report/detail/graph/semiotic/differenceArea.tsx +186 -0
- package/src/kayenta/report/detail/graph/semiotic/histogram.less +22 -0
- package/src/kayenta/report/detail/graph/semiotic/histogram.tsx +251 -0
- package/src/kayenta/report/detail/graph/semiotic/index.tsx +19 -0
- package/src/kayenta/report/detail/graph/semiotic/noValidDataSign.less +5 -0
- package/src/kayenta/report/detail/graph/semiotic/noValidDataSign.tsx +10 -0
- package/src/kayenta/report/detail/graph/semiotic/secondaryTSXAxis.less +6 -0
- package/src/kayenta/report/detail/graph/semiotic/secondaryTSXAxis.tsx +58 -0
- package/src/kayenta/report/detail/graph/semiotic/semiotic.service.ts +32 -0
- package/src/kayenta/report/detail/graph/semiotic/semioticGraph.less +53 -0
- package/src/kayenta/report/detail/graph/semiotic/semioticGraph.tsx +49 -0
- package/src/kayenta/report/detail/graph/semiotic/timeSeries.less +42 -0
- package/src/kayenta/report/detail/graph/semiotic/timeSeries.tsx +473 -0
- package/src/kayenta/report/detail/graph/semiotic/tooltip.tsx +55 -0
- package/src/kayenta/report/detail/graph/semiotic/utils.ts +90 -0
- package/src/kayenta/report/detail/graphTypeSelector.less +4 -0
- package/src/kayenta/report/detail/graphTypeSelector.tsx +50 -0
- package/src/kayenta/report/detail/groupScores.tsx +68 -0
- package/src/kayenta/report/detail/header.less +70 -0
- package/src/kayenta/report/detail/header.tsx +39 -0
- package/src/kayenta/report/detail/headerArrow.tsx +13 -0
- package/src/kayenta/report/detail/loadStates.tsx +31 -0
- package/src/kayenta/report/detail/metricResultActions.less +29 -0
- package/src/kayenta/report/detail/metricResultActions.tsx +87 -0
- package/src/kayenta/report/detail/metricResultClassification.tsx +22 -0
- package/src/kayenta/report/detail/metricResultDetail.tsx +20 -0
- package/src/kayenta/report/detail/metricResultDetailLayout.tsx +19 -0
- package/src/kayenta/report/detail/metricResultDeviation.tsx +25 -0
- package/src/kayenta/report/detail/metricResultStats.less +9 -0
- package/src/kayenta/report/detail/metricResultStats.tsx +120 -0
- package/src/kayenta/report/detail/metricResults.less +12 -0
- package/src/kayenta/report/detail/metricResults.tsx +52 -0
- package/src/kayenta/report/detail/metricResultsClassificationFilters.tsx +65 -0
- package/src/kayenta/report/detail/metricResultsColumns.tsx +27 -0
- package/src/kayenta/report/detail/metricResultsList.less +44 -0
- package/src/kayenta/report/detail/metricResultsList.tsx +120 -0
- package/src/kayenta/report/detail/metricSetPairLoadStates.tsx +22 -0
- package/src/kayenta/report/detail/multipleResultsTable.tsx +81 -0
- package/src/kayenta/report/detail/reportException.tsx +57 -0
- package/src/kayenta/report/detail/reportExplanation.less +12 -0
- package/src/kayenta/report/detail/reportExplanation.tsx +32 -0
- package/src/kayenta/report/detail/reportMetadata.tsx +167 -0
- package/src/kayenta/report/detail/reportScores.less +47 -0
- package/src/kayenta/report/detail/reportScores.tsx +80 -0
- package/src/kayenta/report/detail/score.tsx +33 -0
- package/src/kayenta/report/detail/sourceLinks.tsx +69 -0
- package/src/kayenta/report/list/configLink.tsx +32 -0
- package/src/kayenta/report/list/executionList.less +7 -0
- package/src/kayenta/report/list/loadStates.tsx +32 -0
- package/src/kayenta/report/list/pipelineLink.tsx +15 -0
- package/src/kayenta/report/list/reportLink.tsx +33 -0
- package/src/kayenta/report/list/table.tsx +309 -0
- package/src/kayenta/report/report.tsx +11 -0
- package/src/kayenta/selectors/filterTemplatesSelectors.ts +87 -0
- package/src/kayenta/selectors/index.ts +62 -0
- package/src/kayenta/service/canaryConfig.service.ts +122 -0
- package/src/kayenta/service/canaryRun.service.ts +60 -0
- package/src/kayenta/service/delegateFactory.ts +24 -0
- package/src/kayenta/service/metricsServiceMetadata.service.ts +9 -0
- package/src/kayenta/stages/kayentaStage/AnalysisType.spec.tsx +47 -0
- package/src/kayenta/stages/kayentaStage/AnalysisType.tsx +49 -0
- package/src/kayenta/stages/kayentaStage/CanaryExecutionLabel.tsx +26 -0
- package/src/kayenta/stages/kayentaStage/analysisType.component.ts +12 -0
- package/src/kayenta/stages/kayentaStage/canaryRunSummaries.component.ts +12 -0
- package/src/kayenta/stages/kayentaStage/canaryRunSummaries.less +5 -0
- package/src/kayenta/stages/kayentaStage/canaryRunSummaries.tsx +136 -0
- package/src/kayenta/stages/kayentaStage/forAnalysisType.component.ts +45 -0
- package/src/kayenta/stages/kayentaStage/kayentaStage.controller.ts +789 -0
- package/src/kayenta/stages/kayentaStage/kayentaStage.html +528 -0
- package/src/kayenta/stages/kayentaStage/kayentaStage.less +5 -0
- package/src/kayenta/stages/kayentaStage/kayentaStage.transformer.ts +179 -0
- package/src/kayenta/stages/kayentaStage/kayentaStage.ts +221 -0
- package/src/kayenta/stages/kayentaStage/kayentaStageConfigSection.component.ts +21 -0
- package/src/kayenta/stages/kayentaStage/kayentaStageExecutionDetails.controller.ts +88 -0
- package/src/kayenta/stages/kayentaStage/kayentaStageExecutionDetails.html +114 -0
- package/src/kayenta/stages/kayentaStage/kayentaStageExecutionDetails.less +6 -0
- package/src/kayenta/stages/kayentaStage/stageTypes.ts +5 -0
- package/src/kayenta/utils/duration.spec.ts +69 -0
- package/src/kayenta/utils/duration.ts +48 -0
- package/src/lazy.ts +29 -0
- package/src/stub.ts +60 -0
- package/tsconfig.json +11 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { validateTemplate } from 'kayenta/edit/filterTemplatesValidation';
|
|
2
|
+
import { PrometheusQueryType } from 'kayenta/metricStore/prometheus/domain/IPrometheusCanaryMetricSetQueryConfig';
|
|
3
|
+
import {
|
|
4
|
+
getPrometheusQueryType,
|
|
5
|
+
prometheusQueryTypeToTransformFunction,
|
|
6
|
+
} from 'kayenta/metricStore/prometheus/queryTypeSelectors';
|
|
7
|
+
import { ICanaryState } from 'kayenta/reducers';
|
|
8
|
+
import {
|
|
9
|
+
configTemplatesSelector,
|
|
10
|
+
editingMetricSelector,
|
|
11
|
+
editingTemplateSelector,
|
|
12
|
+
metricListSelector,
|
|
13
|
+
} from 'kayenta/selectors';
|
|
14
|
+
import { get, identity, isEmpty } from 'lodash';
|
|
15
|
+
import { createSelector } from 'reselect';
|
|
16
|
+
|
|
17
|
+
// TODO(mneterval): More elegant separation of prometheus-specific logic
|
|
18
|
+
|
|
19
|
+
export interface ITemplateTransformFunctions {
|
|
20
|
+
fromValue: (template: string) => string;
|
|
21
|
+
toValue: (template: string) => string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Map of provider-specific query type to methods that transform templates between how they are persisted and displayed in the UI
|
|
25
|
+
export const queryTypeToTransformFunctions: { [queryType: string]: ITemplateTransformFunctions } = {
|
|
26
|
+
...prometheusQueryTypeToTransformFunction,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const queryTypeSelector = createSelector(
|
|
30
|
+
(state: ICanaryState) => state.selectedConfig.editingMetric,
|
|
31
|
+
(editingMetric) => {
|
|
32
|
+
return editingMetric && editingMetric.query.serviceType === 'prometheus'
|
|
33
|
+
? getPrometheusQueryType(editingMetric)
|
|
34
|
+
: null;
|
|
35
|
+
},
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
export const transformInlineTemplateForDisplay = createSelector(queryTypeSelector, (queryType) =>
|
|
39
|
+
get(queryTypeToTransformFunctions, [queryType, 'fromValue'], identity),
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
export const transformInlineTemplateForSave = createSelector(queryTypeSelector, (queryType) =>
|
|
43
|
+
get(queryTypeToTransformFunctions, [queryType, 'toValue'], identity),
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
export const selectedTemplateNameSelector = (state: ICanaryState): string =>
|
|
47
|
+
get(state, 'selectedConfig.editingMetric.query.customFilterTemplate');
|
|
48
|
+
|
|
49
|
+
export const inlineTemplateValueSelector = createSelector(
|
|
50
|
+
(state: ICanaryState) => get(state, 'selectedConfig.editingMetric.query.customInlineTemplate'),
|
|
51
|
+
transformInlineTemplateForDisplay,
|
|
52
|
+
(template: string, transformer: (template: string) => string): string => transformer(template),
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
export const editingTemplateValidationSelector = createSelector(
|
|
56
|
+
editingTemplateSelector,
|
|
57
|
+
configTemplatesSelector,
|
|
58
|
+
metricListSelector,
|
|
59
|
+
editingMetricSelector,
|
|
60
|
+
(editingTemplate, configTemplates, metricList, editingMetric) =>
|
|
61
|
+
validateTemplate({
|
|
62
|
+
editingTemplate,
|
|
63
|
+
configTemplates,
|
|
64
|
+
metricList,
|
|
65
|
+
editingMetric,
|
|
66
|
+
}),
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
export const isFilterTemplateValidSelector = createSelector(editingTemplateValidationSelector, (validation) =>
|
|
70
|
+
isEmpty(Object.keys(validation.errors)),
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
export const isInlineTemplateValidSelector = createSelector(inlineTemplateValueSelector, (value) => !isEmpty(value));
|
|
74
|
+
|
|
75
|
+
const inlineTemplateQueryTypes = [PrometheusQueryType.PROMQL];
|
|
76
|
+
|
|
77
|
+
export const useInlineTemplateEditorSelector = createSelector(queryTypeSelector, (queryType) =>
|
|
78
|
+
inlineTemplateQueryTypes.includes(queryType),
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
export const isTemplateValidSelector = createSelector(
|
|
82
|
+
useInlineTemplateEditorSelector,
|
|
83
|
+
isFilterTemplateValidSelector,
|
|
84
|
+
isInlineTemplateValidSelector,
|
|
85
|
+
(isInlineTemplate, isFilterTemplateValid, isInlineTemplateValid) =>
|
|
86
|
+
isInlineTemplate ? isInlineTemplateValid : isFilterTemplateValid,
|
|
87
|
+
);
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { ICanaryConfig, ICanaryMetricConfig } from 'kayenta/domain/index';
|
|
2
|
+
import { get } from 'lodash';
|
|
3
|
+
import { createSelector } from 'reselect';
|
|
4
|
+
|
|
5
|
+
import { ICanaryExecutionStatusResult } from '../domain/ICanaryExecutionStatusResult';
|
|
6
|
+
import { validateMetric } from '../edit/editMetricValidation';
|
|
7
|
+
import { ICanaryState } from '../reducers/index';
|
|
8
|
+
|
|
9
|
+
export const runSelector = (state: ICanaryState): ICanaryExecutionStatusResult => state.selectedRun.run;
|
|
10
|
+
|
|
11
|
+
export const judgeResultSelector = createSelector(runSelector, (run) => run.result?.judgeResult);
|
|
12
|
+
|
|
13
|
+
export const configIdSelector = createSelector(runSelector, (run) => run.config.id);
|
|
14
|
+
|
|
15
|
+
export const metricResultsSelector = createSelector(runSelector, (run) => run.result?.judgeResult.results ?? []);
|
|
16
|
+
|
|
17
|
+
export const canaryExecutionRequestSelector = createSelector(runSelector, (run) => run.canaryExecutionRequest);
|
|
18
|
+
|
|
19
|
+
export const serializedCanaryConfigSelector = createSelector(runSelector, (run) => run.config);
|
|
20
|
+
|
|
21
|
+
export const serializedGroupWeightsSelector = createSelector(
|
|
22
|
+
serializedCanaryConfigSelector,
|
|
23
|
+
(config: ICanaryConfig) => config.classifier.groupWeights,
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
export const selectedMetricResultIdSelector = (state: ICanaryState): string => state.selectedRun.selectedMetric;
|
|
27
|
+
|
|
28
|
+
export const selectedMetricResultSelector = createSelector(
|
|
29
|
+
selectedMetricResultIdSelector,
|
|
30
|
+
metricResultsSelector,
|
|
31
|
+
(id, results) => results.find((result) => result.id === id),
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
export const selectedMetricConfigSelector = createSelector(
|
|
35
|
+
selectedMetricResultSelector,
|
|
36
|
+
serializedCanaryConfigSelector,
|
|
37
|
+
(metric, config) => config.metrics.find((m) => m.name === metric.name),
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
export const selectedConfigSelector = (state: ICanaryState) => state.selectedConfig.config;
|
|
41
|
+
|
|
42
|
+
export const configTemplatesSelector = createSelector(selectedConfigSelector, (config) =>
|
|
43
|
+
config ? config.templates : null,
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
export const editingTemplateSelector = (state: ICanaryState) => state.selectedConfig.editingTemplate;
|
|
47
|
+
|
|
48
|
+
export const resolveConfigIdFromExecutionId = (state: ICanaryState, executionId: string): string => {
|
|
49
|
+
const executions = get(state, ['data', 'executions', 'data'], []);
|
|
50
|
+
const execution = executions.find((ex) => ex.pipelineId === executionId);
|
|
51
|
+
return execution.canaryConfigId;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const editingMetricSelector = (state: ICanaryState): ICanaryMetricConfig => state.selectedConfig.editingMetric;
|
|
55
|
+
|
|
56
|
+
export const metricListSelector = (state: ICanaryState): ICanaryMetricConfig[] => state.selectedConfig.metricList;
|
|
57
|
+
|
|
58
|
+
export const editingMetricValidationErrorsSelector = createSelector(
|
|
59
|
+
editingMetricSelector,
|
|
60
|
+
metricListSelector,
|
|
61
|
+
validateMetric,
|
|
62
|
+
);
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { CanarySettings } from 'kayenta/canary.settings';
|
|
2
|
+
import {
|
|
3
|
+
ICanaryConfig,
|
|
4
|
+
ICanaryConfigSummary,
|
|
5
|
+
ICanaryConfigUpdateResponse,
|
|
6
|
+
ICanaryMetricConfig,
|
|
7
|
+
IJudge,
|
|
8
|
+
IKayentaAccount,
|
|
9
|
+
} from 'kayenta/domain';
|
|
10
|
+
import { ICanaryState } from 'kayenta/reducers';
|
|
11
|
+
import { omit } from 'lodash';
|
|
12
|
+
|
|
13
|
+
import { REST } from '@spinnaker/core';
|
|
14
|
+
|
|
15
|
+
export function getCanaryConfigById(id: string): PromiseLike<ICanaryConfig> {
|
|
16
|
+
return REST('/v2/canaryConfig')
|
|
17
|
+
.path(id)
|
|
18
|
+
.get()
|
|
19
|
+
.then((config: ICanaryConfig) => ({
|
|
20
|
+
...config,
|
|
21
|
+
id,
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function getCanaryConfigSummaries(...application: string[]): PromiseLike<ICanaryConfigSummary[]> {
|
|
26
|
+
return REST('/v2/canaryConfig').query({ application }).get();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function updateCanaryConfig(config: ICanaryConfig): PromiseLike<ICanaryConfigUpdateResponse> {
|
|
30
|
+
return REST('/v2/canaryConfig').path(config.id).put(config);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function createCanaryConfig(config: ICanaryConfig): PromiseLike<ICanaryConfigUpdateResponse> {
|
|
34
|
+
return REST('/v2/canaryConfig').post(config);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function deleteCanaryConfig(id: string): PromiseLike<void> {
|
|
38
|
+
return REST('/v2/canaryConfig').path(id).delete();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function listJudges(): PromiseLike<IJudge[]> {
|
|
42
|
+
return REST('/v2/canaries/judges')
|
|
43
|
+
.get()
|
|
44
|
+
.then((judges: IJudge[]) => judges.filter((judge) => judge.visible));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function listKayentaAccounts(): PromiseLike<IKayentaAccount[]> {
|
|
48
|
+
return REST('/v2/canaries/credentials').useCache().get();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Not sure if this is the right way to go about this. We have pieces of the config
|
|
52
|
+
// living on different parts of the store. Before, e.g., updating the config, we should
|
|
53
|
+
// reconstitute it into a single object that reflects the user's changes.
|
|
54
|
+
export function mapStateToConfig(state: ICanaryState): ICanaryConfig {
|
|
55
|
+
const { selectedConfig } = state;
|
|
56
|
+
if (selectedConfig.config) {
|
|
57
|
+
return {
|
|
58
|
+
...selectedConfig.config,
|
|
59
|
+
judge: selectedConfig.judge.judgeConfig,
|
|
60
|
+
metrics: selectedConfig.metricList.map((metric) => omit(metric, 'id')),
|
|
61
|
+
classifier: {
|
|
62
|
+
...selectedConfig.config.classifier,
|
|
63
|
+
groupWeights: selectedConfig.group.groupWeights,
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
} else {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function buildNewConfig(state: ICanaryState): ICanaryConfig {
|
|
72
|
+
let configName = 'new-config';
|
|
73
|
+
let i = 1;
|
|
74
|
+
while ((state.data.configSummaries || []).some((summary) => summary.name === configName)) {
|
|
75
|
+
configName = `new-config-${i}`;
|
|
76
|
+
i++;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
name: configName,
|
|
81
|
+
applications: [state.data.application.name],
|
|
82
|
+
description: '',
|
|
83
|
+
isNew: true,
|
|
84
|
+
metrics: [] as ICanaryMetricConfig[],
|
|
85
|
+
configVersion: '1',
|
|
86
|
+
templates: {},
|
|
87
|
+
classifier: {
|
|
88
|
+
groupWeights: {} as { [key: string]: number },
|
|
89
|
+
},
|
|
90
|
+
judge: {
|
|
91
|
+
name: CanarySettings.defaultJudge || 'NetflixACAJudge-v1.0',
|
|
92
|
+
judgeConfigurations: {},
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function buildConfigCopy(state: ICanaryState): ICanaryConfig {
|
|
98
|
+
const config: ICanaryConfig = {
|
|
99
|
+
...mapStateToConfig(state),
|
|
100
|
+
applications: [state.data.application.name], // Copy into current application.
|
|
101
|
+
};
|
|
102
|
+
if (!config) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Probably a rare case, but someone could be lazy about naming their configs.
|
|
107
|
+
let configName = `${config.name}-copy`;
|
|
108
|
+
let i = 1;
|
|
109
|
+
while ((state.data.configSummaries || []).some((summary) => summary.name === configName)) {
|
|
110
|
+
configName = `${config.name}-copy-${i}`;
|
|
111
|
+
i++;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return omit(
|
|
115
|
+
{
|
|
116
|
+
...config,
|
|
117
|
+
name: configName,
|
|
118
|
+
isNew: true,
|
|
119
|
+
},
|
|
120
|
+
'id',
|
|
121
|
+
);
|
|
122
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { CanarySettings } from 'kayenta/canary.settings';
|
|
2
|
+
import {
|
|
3
|
+
ICanaryExecutionRequest,
|
|
4
|
+
ICanaryExecutionRequestParams,
|
|
5
|
+
ICanaryExecutionResponse,
|
|
6
|
+
ICanaryExecutionStatusResult,
|
|
7
|
+
IMetricSetPair,
|
|
8
|
+
} from 'kayenta/domain';
|
|
9
|
+
|
|
10
|
+
import { ReactInjector, REST } from '@spinnaker/core';
|
|
11
|
+
|
|
12
|
+
export const getCanaryRun = (configId: string, canaryExecutionId: string): PromiseLike<ICanaryExecutionStatusResult> =>
|
|
13
|
+
REST('/v2/canaries/canary')
|
|
14
|
+
.path(configId, canaryExecutionId)
|
|
15
|
+
.query({ storageAccountName: CanarySettings.storageAccountName })
|
|
16
|
+
.useCache()
|
|
17
|
+
.get()
|
|
18
|
+
.then((run: ICanaryExecutionStatusResult) => {
|
|
19
|
+
const { config } = run;
|
|
20
|
+
config.id = configId;
|
|
21
|
+
run.id = canaryExecutionId;
|
|
22
|
+
run.result?.judgeResult.results.sort((a, b) => a.name.localeCompare(b.name));
|
|
23
|
+
return run;
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export const startCanaryRun = (
|
|
27
|
+
configId: string,
|
|
28
|
+
executionRequest: ICanaryExecutionRequest,
|
|
29
|
+
params: ICanaryExecutionRequestParams = {},
|
|
30
|
+
): PromiseLike<ICanaryExecutionResponse> => {
|
|
31
|
+
return REST('/v2/canaries/canary')
|
|
32
|
+
.path(configId)
|
|
33
|
+
.query(params as any)
|
|
34
|
+
.post(executionRequest);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const getMetricSetPair = (metricSetPairListId: string, metricSetPairId: string): PromiseLike<IMetricSetPair> =>
|
|
38
|
+
REST('/v2/canaries/metricSetPairList')
|
|
39
|
+
.path(metricSetPairListId)
|
|
40
|
+
.query({ storageAccountName: CanarySettings.storageAccountName })
|
|
41
|
+
.useCache()
|
|
42
|
+
.get()
|
|
43
|
+
.then((list: IMetricSetPair[]) => list.find((pair) => pair.id === metricSetPairId));
|
|
44
|
+
|
|
45
|
+
export const listCanaryExecutions = (application: string): PromiseLike<ICanaryExecutionStatusResult[]> => {
|
|
46
|
+
const limit = ReactInjector.$stateParams.count || 20;
|
|
47
|
+
return REST('/v2/canaries').path(application, 'executions').query({ limit }).get();
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const getHealthLabel = (health: string, result: string): string => {
|
|
51
|
+
const healthLC = (health || '').toLowerCase();
|
|
52
|
+
const resultLC = (result || '').toLowerCase();
|
|
53
|
+
return healthLC === 'unhealthy'
|
|
54
|
+
? 'unhealthy'
|
|
55
|
+
: resultLC === 'success'
|
|
56
|
+
? 'healthy'
|
|
57
|
+
: resultLC === 'failure'
|
|
58
|
+
? 'failing'
|
|
59
|
+
: 'unknown';
|
|
60
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface IDelegate {
|
|
2
|
+
name: string;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export interface IDelegateService<T extends IDelegate> {
|
|
6
|
+
register(delegate: T): void;
|
|
7
|
+
getDelegate(name: string): T;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const buildDelegateService = <T extends IDelegate>() => {
|
|
11
|
+
class DelegateService {
|
|
12
|
+
private delegates: T[] = [];
|
|
13
|
+
|
|
14
|
+
public register(delegate: T): void {
|
|
15
|
+
this.delegates.push(delegate);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public getDelegate(name: string): T {
|
|
19
|
+
return this.delegates.find((d) => d.name === name);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return new DelegateService() as IDelegateService<T>;
|
|
24
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { IMetricsServiceMetadata } from 'kayenta/domain/IMetricsServiceMetadata';
|
|
2
|
+
|
|
3
|
+
import { REST } from '@spinnaker/core';
|
|
4
|
+
|
|
5
|
+
export const listMetricsServiceMetadata = (
|
|
6
|
+
filter?: string,
|
|
7
|
+
metricsAccountName?: string,
|
|
8
|
+
): PromiseLike<IMetricsServiceMetadata[]> =>
|
|
9
|
+
REST('/v2/canaries/metadata/metricsService').query({ filter, metricsAccountName }).get();
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { mount, ReactWrapper } from 'enzyme';
|
|
2
|
+
import { KayentaAnalysisType } from 'kayenta/domain';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
|
|
5
|
+
import { AnalysisType, AnalysisTypeWarning, IAnalysisTypeProps } from './AnalysisType';
|
|
6
|
+
|
|
7
|
+
describe('<AnalysisType />', () => {
|
|
8
|
+
const component = (props: IAnalysisTypeProps) => mount((<AnalysisType {...props} />) as any);
|
|
9
|
+
|
|
10
|
+
const tests = [
|
|
11
|
+
{
|
|
12
|
+
it: 'only includes one radio button if only one analysis type is provided',
|
|
13
|
+
props: {
|
|
14
|
+
analysisTypes: [KayentaAnalysisType.Retrospective],
|
|
15
|
+
selectedType: KayentaAnalysisType.Retrospective,
|
|
16
|
+
},
|
|
17
|
+
assertion: (wrapper: ReactWrapper) => {
|
|
18
|
+
expect(wrapper.find('input[type="radio"]').length).toEqual(1);
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
it: 'only includes two radio buttons if only two analysis types are provided',
|
|
23
|
+
props: {
|
|
24
|
+
analysisTypes: [KayentaAnalysisType.Retrospective, KayentaAnalysisType.RealTime],
|
|
25
|
+
selectedType: KayentaAnalysisType.Retrospective,
|
|
26
|
+
},
|
|
27
|
+
assertion: (wrapper: ReactWrapper) => {
|
|
28
|
+
expect(wrapper.find('input[type="radio"]').length).toEqual(2);
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
it: 'renders a warning if selected analysis type is not one of the provided analysis types',
|
|
33
|
+
props: {
|
|
34
|
+
analysisTypes: [KayentaAnalysisType.Retrospective, KayentaAnalysisType.RealTime],
|
|
35
|
+
selectedType: KayentaAnalysisType.RealTimeAutomatic,
|
|
36
|
+
},
|
|
37
|
+
assertion: (wrapper: ReactWrapper) => {
|
|
38
|
+
expect(wrapper.find('input[type="radio"]').length).toEqual(2);
|
|
39
|
+
expect(wrapper.find(AnalysisTypeWarning).exists()).toBeTruthy();
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
tests.forEach((test) => {
|
|
45
|
+
it(test.it, () => test.assertion(component(test.props)));
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { KayentaAnalysisType } from 'kayenta/domain';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
|
|
4
|
+
import { noop } from '@spinnaker/core';
|
|
5
|
+
|
|
6
|
+
export interface IAnalysisTypeProps {
|
|
7
|
+
analysisTypes: KayentaAnalysisType[];
|
|
8
|
+
selectedType: KayentaAnalysisType;
|
|
9
|
+
onChange?: (type: KayentaAnalysisType) => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const AnalysisTypeWarning = () => (
|
|
13
|
+
<div className="alert alert-warning">
|
|
14
|
+
The analysis type you've selected isn't supported by any of this application's cloud providers. Please select a
|
|
15
|
+
different type.
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
export const AnalysisTypeRadioButton = ({
|
|
20
|
+
selectedType,
|
|
21
|
+
analysisTypes,
|
|
22
|
+
onChange = noop,
|
|
23
|
+
label,
|
|
24
|
+
type,
|
|
25
|
+
}: IAnalysisTypeProps & { label: string; type: KayentaAnalysisType }) => {
|
|
26
|
+
if (!analysisTypes.includes(type)) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
return (
|
|
30
|
+
<div className="radio">
|
|
31
|
+
<label>
|
|
32
|
+
<input type="radio" name="analysisType" checked={selectedType === type} onChange={() => onChange(type)} />
|
|
33
|
+
{label}
|
|
34
|
+
</label>
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const AnalysisType = (props: IAnalysisTypeProps) => {
|
|
40
|
+
const { analysisTypes = [], selectedType } = props;
|
|
41
|
+
return (
|
|
42
|
+
<>
|
|
43
|
+
{!analysisTypes.includes(selectedType) && <AnalysisTypeWarning />}
|
|
44
|
+
<AnalysisTypeRadioButton {...props} label="Real Time (Automatic)" type={KayentaAnalysisType.RealTimeAutomatic} />
|
|
45
|
+
<AnalysisTypeRadioButton {...props} label="Real Time (Manual)" type={KayentaAnalysisType.RealTime} />
|
|
46
|
+
<AnalysisTypeRadioButton {...props} label="Retrospective" type={KayentaAnalysisType.Retrospective} />
|
|
47
|
+
</>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { CanaryScore } from 'kayenta/components/canaryScore';
|
|
2
|
+
import { get } from 'lodash';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
|
|
5
|
+
import { IExecutionStage, IExecutionStageSummary } from '@spinnaker/core';
|
|
6
|
+
|
|
7
|
+
export interface ICanaryExecutionLabelProps {
|
|
8
|
+
stage: IExecutionStageSummary;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const CanaryExecutionLabel = ({ stage }: ICanaryExecutionLabelProps) => {
|
|
12
|
+
const { overallScore, overallResult } = get<IExecutionStage['context']>(stage, 'masterStage.context', {});
|
|
13
|
+
const score = (
|
|
14
|
+
<CanaryScore
|
|
15
|
+
inverse={true}
|
|
16
|
+
score={overallScore}
|
|
17
|
+
result={overallResult === 'success' ? overallResult : null}
|
|
18
|
+
health={overallResult === 'success' ? null : 'unhealthy'}
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
21
|
+
return (
|
|
22
|
+
<span className="stage-label">
|
|
23
|
+
<span>{stage.name}</span> ({score})
|
|
24
|
+
</span>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { module } from 'angular';
|
|
2
|
+
import { react2angular } from 'react2angular';
|
|
3
|
+
|
|
4
|
+
import { withErrorBoundary } from '@spinnaker/core';
|
|
5
|
+
|
|
6
|
+
import { AnalysisType } from './AnalysisType';
|
|
7
|
+
|
|
8
|
+
export const KAYENTA_ANALYSIS_TYPE_COMPONENT = 'spinnaker.kayenta.analysisType.component';
|
|
9
|
+
module(KAYENTA_ANALYSIS_TYPE_COMPONENT, []).component(
|
|
10
|
+
'kayentaAnalysisType',
|
|
11
|
+
react2angular(withErrorBoundary(AnalysisType, 'kayentaAnalysisType'), ['analysisTypes', 'selectedType', 'onChange']),
|
|
12
|
+
);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { module } from 'angular';
|
|
2
|
+
import { react2angular } from 'react2angular';
|
|
3
|
+
|
|
4
|
+
import { withErrorBoundary } from '@spinnaker/core';
|
|
5
|
+
|
|
6
|
+
import CanaryRunSummaries from './canaryRunSummaries';
|
|
7
|
+
|
|
8
|
+
export const CANARY_RUN_SUMMARIES_COMPONENT = 'spinnaker.kayenta.canaryRunSummaries.component';
|
|
9
|
+
module(CANARY_RUN_SUMMARIES_COMPONENT, []).component(
|
|
10
|
+
'canaryRunSummaries',
|
|
11
|
+
react2angular(withErrorBoundary(CanaryRunSummaries, 'canaryRunSummaries'), ['canaryRuns', 'firstScopeName']),
|
|
12
|
+
);
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { CanaryScore } from 'kayenta/components/canaryScore';
|
|
2
|
+
import Styleguide from 'kayenta/layout/styleguide';
|
|
3
|
+
import { ITableColumn, NativeTable } from 'kayenta/layout/table';
|
|
4
|
+
import { get, has } from 'lodash';
|
|
5
|
+
import * as React from 'react';
|
|
6
|
+
|
|
7
|
+
import { CopyToClipboard, HoverablePopover, IStage, ReactInjector, timestamp } from '@spinnaker/core';
|
|
8
|
+
|
|
9
|
+
import './canaryRunSummaries.less';
|
|
10
|
+
|
|
11
|
+
export interface ICanarySummariesProps {
|
|
12
|
+
canaryRuns: IStage[];
|
|
13
|
+
firstScopeName: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ICanaryRunColumn {
|
|
17
|
+
label?: string;
|
|
18
|
+
width: number;
|
|
19
|
+
getContent: (run: IStage, firstScopeName?: string) => JSX.Element;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default function CanaryRunSummaries({ canaryRuns, firstScopeName }: ICanarySummariesProps) {
|
|
23
|
+
const canaryRunColumns: Array<ITableColumn<IStage>> = [
|
|
24
|
+
{
|
|
25
|
+
label: 'Canary Result',
|
|
26
|
+
getContent: (run) => {
|
|
27
|
+
return (
|
|
28
|
+
<>
|
|
29
|
+
<CanaryScore
|
|
30
|
+
score={run.context.canaryScore}
|
|
31
|
+
health={run.health}
|
|
32
|
+
result={run.result}
|
|
33
|
+
inverse={false}
|
|
34
|
+
className="label"
|
|
35
|
+
/>
|
|
36
|
+
{get(run, ['context', 'warnings'], []).length > 0 && (
|
|
37
|
+
<HoverablePopover template={<CanaryRunWarningMessages messages={run.context.warnings} />}>
|
|
38
|
+
<i className="fa fa-exclamation-triangle" style={{ paddingLeft: '8px' }} />
|
|
39
|
+
</HoverablePopover>
|
|
40
|
+
)}
|
|
41
|
+
</>
|
|
42
|
+
);
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
label: 'Duration',
|
|
47
|
+
getContent: (run) => <span>{run.context.durationString || ' - '}</span>,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
label: 'Last Updated',
|
|
51
|
+
getContent: (run) => <span>{timestamp(run.context.lastUpdated)}</span>,
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
getContent: (run) => {
|
|
55
|
+
const popoverTemplate = <CanaryRunTimestamps canaryRun={run} firstScopeName={firstScopeName} />;
|
|
56
|
+
return (
|
|
57
|
+
<section className="horizontal text-center">
|
|
58
|
+
<div className="flex-1">
|
|
59
|
+
<ReportLink canaryRun={run} />
|
|
60
|
+
</div>
|
|
61
|
+
<div className="flex-1">
|
|
62
|
+
<HoverablePopover template={popoverTemplate}>
|
|
63
|
+
<i className="far fa-clock" />
|
|
64
|
+
</HoverablePopover>
|
|
65
|
+
</div>
|
|
66
|
+
</section>
|
|
67
|
+
);
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<Styleguide className="horizontal flex-1">
|
|
74
|
+
<NativeTable
|
|
75
|
+
rows={canaryRuns}
|
|
76
|
+
className="header-transparent flex-1"
|
|
77
|
+
columns={canaryRunColumns}
|
|
78
|
+
rowKey={(run) => run.id}
|
|
79
|
+
/>
|
|
80
|
+
</Styleguide>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function CanaryRunTimestamps({ canaryRun, firstScopeName }: { canaryRun: IStage; firstScopeName: string }) {
|
|
85
|
+
const toolTipText = 'Copy timestamp to clipboard as UTC instant in ISO-8601 format';
|
|
86
|
+
return (
|
|
87
|
+
<section className="small">
|
|
88
|
+
<ul className="list-unstyled">
|
|
89
|
+
<li>
|
|
90
|
+
<b>Start:</b> {timestamp(Date.parse(canaryRun.context.scopes[firstScopeName].experimentScope.start))}
|
|
91
|
+
<CopyToClipboard
|
|
92
|
+
displayText={false}
|
|
93
|
+
text={canaryRun.context.scopes[firstScopeName].experimentScope.start}
|
|
94
|
+
toolTip={toolTipText}
|
|
95
|
+
/>
|
|
96
|
+
</li>
|
|
97
|
+
<li>
|
|
98
|
+
<b>End:</b> {timestamp(Date.parse(canaryRun.context.scopes[firstScopeName].experimentScope.end))}
|
|
99
|
+
<CopyToClipboard
|
|
100
|
+
displayText={false}
|
|
101
|
+
text={canaryRun.context.scopes[firstScopeName].experimentScope.end}
|
|
102
|
+
toolTip={toolTipText}
|
|
103
|
+
/>
|
|
104
|
+
</li>
|
|
105
|
+
</ul>
|
|
106
|
+
</section>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function ReportLink({ canaryRun }: { canaryRun: IStage }) {
|
|
111
|
+
if (
|
|
112
|
+
!has(canaryRun, 'context.canaryConfigId') ||
|
|
113
|
+
!has(canaryRun, 'context.canaryPipelineExecutionId') ||
|
|
114
|
+
canaryRun.status === 'RUNNING'
|
|
115
|
+
) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const onClick = () =>
|
|
120
|
+
ReactInjector.$state.go('home.applications.application.canary.report.reportDetail', {
|
|
121
|
+
configId: canaryRun.context.canaryConfigId,
|
|
122
|
+
runId: canaryRun.context.canaryPipelineExecutionId,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
return <i className="fa fa-chart-bar clickable" onClick={onClick} />;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function CanaryRunWarningMessages({ messages }: { messages: string[] }) {
|
|
129
|
+
return (
|
|
130
|
+
<div>
|
|
131
|
+
{messages.map((message, i) => (
|
|
132
|
+
<p key={`${i}-${message}`}>{message}</p>
|
|
133
|
+
))}
|
|
134
|
+
</div>
|
|
135
|
+
);
|
|
136
|
+
}
|