@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,716 @@
|
|
|
1
|
+
import { Field, Form, Formik, FormikActions, FormikErrors, FormikProps, FormikTouched } from 'formik';
|
|
2
|
+
import { get } from 'lodash';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { Modal } from 'react-bootstrap';
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
Application,
|
|
8
|
+
HelpField,
|
|
9
|
+
HoverablePopover,
|
|
10
|
+
IModalComponentProps,
|
|
11
|
+
MapEditor,
|
|
12
|
+
ModalClose,
|
|
13
|
+
noop,
|
|
14
|
+
ReactModal,
|
|
15
|
+
SubmitButton,
|
|
16
|
+
TetheredSelect,
|
|
17
|
+
} from '@spinnaker/core';
|
|
18
|
+
|
|
19
|
+
import { CanaryScores } from '../components/canaryScores';
|
|
20
|
+
import { ICanaryConfigSummary, ICanaryExecutionRequest, IKayentaAccount, KayentaAccountType } from '../domain';
|
|
21
|
+
import { startCanaryRun } from '../service/canaryRun.service';
|
|
22
|
+
|
|
23
|
+
const RESOURCE_TYPES = {
|
|
24
|
+
prometheus: [
|
|
25
|
+
{ label: '', value: '' },
|
|
26
|
+
{ label: 'gce_instance', value: 'gce_instance' },
|
|
27
|
+
{ label: 'aws_ec2_instance', value: 'aws_ec2_instance' },
|
|
28
|
+
],
|
|
29
|
+
stackdriver: [
|
|
30
|
+
{ label: 'gce_instance', value: 'gce_instance' },
|
|
31
|
+
{ label: 'aws_ec2_instance', value: 'aws_ec2_instance' },
|
|
32
|
+
{ label: 'gae_app', value: 'gae_app' },
|
|
33
|
+
{ label: 'k8s_container', value: 'k8s_container' },
|
|
34
|
+
{ label: 'k8s_pod', value: 'k8s_pod' },
|
|
35
|
+
{ label: 'k8s_node', value: 'k8s_node' },
|
|
36
|
+
{ label: 'gke_container', value: 'gke_container' },
|
|
37
|
+
{ label: 'https_lb_rule', value: 'https_lb_rule' },
|
|
38
|
+
{ label: 'global', value: 'global' },
|
|
39
|
+
],
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export interface IManualAnalysisModalProps extends IModalComponentProps {
|
|
43
|
+
title: string;
|
|
44
|
+
application: Application;
|
|
45
|
+
accounts: IKayentaAccount[];
|
|
46
|
+
initialValues?: IManualAnalysisModalFormProps;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface IManualAnalysisModalState {
|
|
50
|
+
showAllControlLocations: boolean;
|
|
51
|
+
showAllExperimentLocations: boolean;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface IManualAnalysisModalFormProps {
|
|
55
|
+
configId: string;
|
|
56
|
+
startTime: string;
|
|
57
|
+
endTime: string;
|
|
58
|
+
step: string;
|
|
59
|
+
baselineScope: string;
|
|
60
|
+
canaryScope: string;
|
|
61
|
+
baselineLocation: string;
|
|
62
|
+
canaryLocation: string;
|
|
63
|
+
extendedScopeParams: { [key: string]: string };
|
|
64
|
+
resourceType: string;
|
|
65
|
+
marginalThreshold: string;
|
|
66
|
+
passThreshold: string;
|
|
67
|
+
metricsAccountName: string;
|
|
68
|
+
storageAccountName: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const transformFormPropsToExecutionRequest = (
|
|
72
|
+
values: IManualAnalysisModalFormProps,
|
|
73
|
+
accounts: IKayentaAccount[],
|
|
74
|
+
): ICanaryExecutionRequest => {
|
|
75
|
+
const step = parseInt(values.step, 10);
|
|
76
|
+
const pass = parseInt(values.passThreshold, 10);
|
|
77
|
+
const marginal = parseInt(values.marginalThreshold, 10);
|
|
78
|
+
|
|
79
|
+
const metricsAccount = accounts.find(({ name }) => values.metricsAccountName === name);
|
|
80
|
+
|
|
81
|
+
const accountSpecificParams: { [param: string]: string } = {};
|
|
82
|
+
|
|
83
|
+
if (values.resourceType) {
|
|
84
|
+
accountSpecificParams.resourceType = values.resourceType;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (metricsAccount.type === 'atlas' && !values.extendedScopeParams?.type) {
|
|
88
|
+
accountSpecificParams.type = 'cluster';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const extendedScopeParams = { ...values.extendedScopeParams, ...accountSpecificParams };
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
scopes: {
|
|
95
|
+
default: {
|
|
96
|
+
controlScope: {
|
|
97
|
+
scope: values.baselineScope,
|
|
98
|
+
start: values.startTime,
|
|
99
|
+
end: values.endTime,
|
|
100
|
+
location: values.baselineLocation,
|
|
101
|
+
step,
|
|
102
|
+
extendedScopeParams,
|
|
103
|
+
},
|
|
104
|
+
experimentScope: {
|
|
105
|
+
scope: values.canaryScope,
|
|
106
|
+
start: values.startTime,
|
|
107
|
+
end: values.endTime,
|
|
108
|
+
location: values.canaryLocation,
|
|
109
|
+
step,
|
|
110
|
+
extendedScopeParams,
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
thresholds: {
|
|
115
|
+
pass,
|
|
116
|
+
marginal,
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const defaultValues: IManualAnalysisModalFormProps = {
|
|
122
|
+
configId: null,
|
|
123
|
+
startTime: '',
|
|
124
|
+
endTime: '',
|
|
125
|
+
step: '',
|
|
126
|
+
baselineScope: '',
|
|
127
|
+
canaryScope: '',
|
|
128
|
+
baselineLocation: '',
|
|
129
|
+
canaryLocation: '',
|
|
130
|
+
extendedScopeParams: {},
|
|
131
|
+
resourceType: '',
|
|
132
|
+
marginalThreshold: '',
|
|
133
|
+
passThreshold: '',
|
|
134
|
+
metricsAccountName: '',
|
|
135
|
+
storageAccountName: '',
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export class ManualAnalysisModal extends React.Component<IManualAnalysisModalProps, IManualAnalysisModalState> {
|
|
139
|
+
public static defaultProps: Partial<IManualAnalysisModalProps> = {
|
|
140
|
+
closeModal: noop,
|
|
141
|
+
dismissModal: noop,
|
|
142
|
+
initialValues: defaultValues,
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
public static show(props: IManualAnalysisModalProps): Promise<any> {
|
|
146
|
+
const modalProps = { dialogClassName: 'modal-lg' };
|
|
147
|
+
return ReactModal.show(ManualAnalysisModal, props, modalProps);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
constructor(props: IManualAnalysisModalProps) {
|
|
151
|
+
super(props);
|
|
152
|
+
const { initialValues, accounts } = props;
|
|
153
|
+
const selectedMetricsStore = accounts.find(({ name }) => initialValues.metricsAccountName === name);
|
|
154
|
+
const recommendedLocations = selectedMetricsStore?.recommendedLocations ?? [];
|
|
155
|
+
this.state = {
|
|
156
|
+
showAllControlLocations: !recommendedLocations.includes(initialValues.baselineLocation),
|
|
157
|
+
showAllExperimentLocations: !recommendedLocations.includes(initialValues.canaryLocation),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private validate = (values: IManualAnalysisModalFormProps) => {
|
|
162
|
+
const errors = {} as FormikErrors<IManualAnalysisModalFormProps>;
|
|
163
|
+
let startTime, endTime;
|
|
164
|
+
|
|
165
|
+
if (!values.configId) {
|
|
166
|
+
errors.configId = 'You must choose a canary config';
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (!values.startTime) {
|
|
170
|
+
errors.startTime = 'You must provide a start time';
|
|
171
|
+
} else {
|
|
172
|
+
try {
|
|
173
|
+
startTime = new Date(values.startTime).getTime();
|
|
174
|
+
} catch (e) {
|
|
175
|
+
errors.startTime = 'Invalid start time. Time must be in ISO-8601 format, e.g. 2018-07-12T22:28:29Z';
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (!values.endTime) {
|
|
180
|
+
errors.endTime = 'You must provide an end time';
|
|
181
|
+
} else {
|
|
182
|
+
try {
|
|
183
|
+
endTime = new Date(values.endTime).getTime();
|
|
184
|
+
} catch (e) {
|
|
185
|
+
errors.endTime = 'Invalid start time. Time must be in ISO-8601 format, e.g. 2018-07-12T22:28:29Z';
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (startTime && endTime && endTime <= startTime) {
|
|
190
|
+
errors.endTime = 'End time must be after start time';
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (!values.step) {
|
|
194
|
+
errors.step = 'You must provide a step value';
|
|
195
|
+
} else if (parseInt(values.step, 10) < 1) {
|
|
196
|
+
errors.step = 'Invalid step value';
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (!values.baselineScope) {
|
|
200
|
+
errors.baselineScope = 'You must provide a baseline scope';
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (!values.canaryScope) {
|
|
204
|
+
errors.canaryScope = 'You must provide a canary scope';
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (!values.baselineLocation) {
|
|
208
|
+
errors.baselineLocation = 'You must provide a baseline location';
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (!values.canaryLocation) {
|
|
212
|
+
errors.canaryLocation = 'You must provide a canary location';
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const marginalThreshold = parseInt(values.marginalThreshold, 10);
|
|
216
|
+
const passThreshold = parseInt(values.passThreshold, 10);
|
|
217
|
+
|
|
218
|
+
if (!values.marginalThreshold) {
|
|
219
|
+
errors.marginalThreshold = 'You must provide a marginal score threshold';
|
|
220
|
+
} else if (marginalThreshold > passThreshold) {
|
|
221
|
+
errors.marginalThreshold = 'Marginal threshold must be greater than passing threshold';
|
|
222
|
+
} else if (marginalThreshold < 1) {
|
|
223
|
+
errors.marginalThreshold = 'Marginal threshold must be positive';
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (!values.passThreshold) {
|
|
227
|
+
errors.passThreshold = 'You must provide a passing score threshold';
|
|
228
|
+
} else if (passThreshold > 100) {
|
|
229
|
+
errors.passThreshold = 'Passing threshold cannot be greater than 100';
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (!values.metricsAccountName) {
|
|
233
|
+
errors.metricsAccountName = 'You must choose a metrics account';
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (!values.storageAccountName) {
|
|
237
|
+
errors.storageAccountName = 'You must choose a storage account';
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return errors;
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
private submit = (values: IManualAnalysisModalFormProps, actions: FormikActions<IManualAnalysisModalFormProps>) => {
|
|
244
|
+
const { configId, metricsAccountName, storageAccountName } = values;
|
|
245
|
+
const { application, accounts } = this.props;
|
|
246
|
+
const executionRequest = transformFormPropsToExecutionRequest(values, accounts);
|
|
247
|
+
|
|
248
|
+
startCanaryRun(configId, executionRequest, {
|
|
249
|
+
application: application.name,
|
|
250
|
+
metricsAccountName,
|
|
251
|
+
storageAccountName,
|
|
252
|
+
})
|
|
253
|
+
.then(() => {
|
|
254
|
+
actions.setSubmitting(false);
|
|
255
|
+
actions.setStatus({ succeeded: true });
|
|
256
|
+
})
|
|
257
|
+
.catch((error) => {
|
|
258
|
+
actions.setSubmitting(false);
|
|
259
|
+
actions.setStatus({ succeeded: false, error });
|
|
260
|
+
});
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
public render() {
|
|
264
|
+
const { dismissModal, title, application, accounts, initialValues } = this.props;
|
|
265
|
+
const { showAllControlLocations, showAllExperimentLocations } = this.state;
|
|
266
|
+
|
|
267
|
+
const canaryConfigs: ICanaryConfigSummary[] = application
|
|
268
|
+
.getDataSource('canaryConfigs')
|
|
269
|
+
.data.slice()
|
|
270
|
+
.sort((a: ICanaryConfigSummary, b: ICanaryConfigSummary) => a.name.localeCompare(b.name));
|
|
271
|
+
const metricsStores = accounts.filter(({ supportedTypes }) =>
|
|
272
|
+
supportedTypes.includes(KayentaAccountType.MetricsStore),
|
|
273
|
+
);
|
|
274
|
+
const objectStores = accounts.filter(({ supportedTypes }) =>
|
|
275
|
+
supportedTypes.includes(KayentaAccountType.ObjectStore),
|
|
276
|
+
);
|
|
277
|
+
// TODO: support a scope name besides 'default'.
|
|
278
|
+
const showAdvancedSettings = metricsStores.length > 1 || objectStores.length > 1;
|
|
279
|
+
|
|
280
|
+
return (
|
|
281
|
+
<Formik
|
|
282
|
+
initialValues={{
|
|
283
|
+
...initialValues,
|
|
284
|
+
metricsAccountName: get(metricsStores[0], 'name', ''),
|
|
285
|
+
storageAccountName: get(objectStores[0], 'name', ''),
|
|
286
|
+
}}
|
|
287
|
+
onSubmit={this.submit}
|
|
288
|
+
validate={this.validate}
|
|
289
|
+
isInitialValid={!!initialValues.configId} // if we have a config id initially, everything else should be valid
|
|
290
|
+
render={({
|
|
291
|
+
values,
|
|
292
|
+
touched,
|
|
293
|
+
isValid,
|
|
294
|
+
isSubmitting,
|
|
295
|
+
status,
|
|
296
|
+
setFieldValue,
|
|
297
|
+
setFieldTouched,
|
|
298
|
+
errors,
|
|
299
|
+
}: FormikProps<IManualAnalysisModalFormProps>) => {
|
|
300
|
+
if (get(status, 'succeeded') === true) {
|
|
301
|
+
return (
|
|
302
|
+
<>
|
|
303
|
+
<ModalClose dismiss={dismissModal} />
|
|
304
|
+
<Modal.Header>{<h3>Analysis Started</h3>}</Modal.Header>
|
|
305
|
+
<Modal.Body>
|
|
306
|
+
<p>Analysis started — check the list of reports in a few minutes to view results</p>
|
|
307
|
+
</Modal.Body>
|
|
308
|
+
<Modal.Footer className="horizontal right">
|
|
309
|
+
<button className="btn btn-primary" onClick={dismissModal} type="button">
|
|
310
|
+
Got it
|
|
311
|
+
</button>
|
|
312
|
+
</Modal.Footer>
|
|
313
|
+
</>
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const selectedMetricsStore = metricsStores.find(({ name }) => values.metricsAccountName === name);
|
|
318
|
+
const selectedObjectStore = objectStores.find(({ name }) => values.storageAccountName === name);
|
|
319
|
+
|
|
320
|
+
const recommendedLocations = get(selectedMetricsStore, 'recommendedLocations', []);
|
|
321
|
+
const { canaryLocation, baselineLocation } = initialValues;
|
|
322
|
+
const locations = get(selectedMetricsStore, 'locations', []);
|
|
323
|
+
// Almost every report generated for Atlas uses a simple region for its location. On the backend, Kayenta
|
|
324
|
+
// performs amazing feats to reconcile the location with an Atlas backend, considering the values of several
|
|
325
|
+
// extended params. We are not going to reverse engineer that logic here in order to pick a predefined value
|
|
326
|
+
// from the dropdown: if a location was valid to generate the report, it'll probably be valid when we
|
|
327
|
+
// regenerate it.
|
|
328
|
+
if (canaryLocation && !locations.includes(canaryLocation)) {
|
|
329
|
+
locations.push(canaryLocation);
|
|
330
|
+
}
|
|
331
|
+
if (baselineLocation && !locations.includes(baselineLocation)) {
|
|
332
|
+
locations.push(baselineLocation);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const hasLocationChoices = recommendedLocations.length > 0 || locations.length > 0;
|
|
336
|
+
|
|
337
|
+
return (
|
|
338
|
+
<Form className="form-horizontal">
|
|
339
|
+
<ModalClose dismiss={dismissModal} />
|
|
340
|
+
<Modal.Header>{title && <h3>{title}</h3>}</Modal.Header>
|
|
341
|
+
<Modal.Body>
|
|
342
|
+
<div className="row">
|
|
343
|
+
<div className="col-md-12">
|
|
344
|
+
<div className="container-fluid form-horizontal">
|
|
345
|
+
<h5>Analysis Configuration</h5>
|
|
346
|
+
<div className="horizontal-rule" />
|
|
347
|
+
<div className="form-group">
|
|
348
|
+
<div className="col-md-3 sm-label-right">Config Name</div>
|
|
349
|
+
<div className="col-md-7">
|
|
350
|
+
<TetheredSelect
|
|
351
|
+
value={values.configId}
|
|
352
|
+
options={canaryConfigs.map(({ id, name }) => ({ label: name, value: id }))}
|
|
353
|
+
onChange={(item: { label: string; value: string }) =>
|
|
354
|
+
setFieldValue('configId', (item && item.value) || null)
|
|
355
|
+
}
|
|
356
|
+
/>
|
|
357
|
+
</div>
|
|
358
|
+
</div>
|
|
359
|
+
<div className="form-group">
|
|
360
|
+
<div className="col-md-3 sm-label-right">
|
|
361
|
+
Start Time <HelpField id="pipeline.config.canary.startTimeIso" />
|
|
362
|
+
</div>
|
|
363
|
+
<div className="col-md-7">
|
|
364
|
+
<ValidatedField errors={errors} touched={touched} name="startTime" />
|
|
365
|
+
</div>
|
|
366
|
+
</div>
|
|
367
|
+
<div className="form-group">
|
|
368
|
+
<div className="col-md-3 sm-label-right">
|
|
369
|
+
End Time <HelpField id="pipeline.config.canary.endTimeIso" />
|
|
370
|
+
</div>
|
|
371
|
+
<div className="col-md-7">
|
|
372
|
+
<ValidatedField errors={errors} touched={touched} name="endTime" />
|
|
373
|
+
</div>
|
|
374
|
+
</div>
|
|
375
|
+
<div className="form-group">
|
|
376
|
+
<div className="col-md-3 sm-label-right">Step</div>
|
|
377
|
+
<div className="col-md-7">
|
|
378
|
+
<ValidatedField
|
|
379
|
+
errors={errors}
|
|
380
|
+
touched={touched}
|
|
381
|
+
type="number"
|
|
382
|
+
style={{ width: '60px', display: 'inline-block' }}
|
|
383
|
+
name="step"
|
|
384
|
+
/>
|
|
385
|
+
<span className="form-control-static"> seconds</span>
|
|
386
|
+
</div>
|
|
387
|
+
</div>
|
|
388
|
+
<h5>Baseline + Canary Pair</h5>
|
|
389
|
+
<div className="horizontal-rule" />
|
|
390
|
+
<div className="form-group">
|
|
391
|
+
<div className="col-md-3 sm-label-right">
|
|
392
|
+
Baseline <HelpField id="pipeline.config.canary.baselineGroup" />
|
|
393
|
+
</div>
|
|
394
|
+
<div className="col-md-7">
|
|
395
|
+
<ValidatedField name="baselineScope" errors={errors} touched={touched} />
|
|
396
|
+
</div>
|
|
397
|
+
</div>
|
|
398
|
+
<div className="form-group">
|
|
399
|
+
<div className="col-md-3 sm-label-right">
|
|
400
|
+
Baseline Location <HelpField id="pipeline.config.canary.baselineLocation" />
|
|
401
|
+
</div>
|
|
402
|
+
<div className="col-md-7">
|
|
403
|
+
<LocationField
|
|
404
|
+
hasLocationChoices={hasLocationChoices}
|
|
405
|
+
showAll={showAllControlLocations}
|
|
406
|
+
recommendedLocations={recommendedLocations}
|
|
407
|
+
locations={locations}
|
|
408
|
+
value={values.baselineLocation}
|
|
409
|
+
field="baselineLocation"
|
|
410
|
+
errors={errors}
|
|
411
|
+
touched={touched}
|
|
412
|
+
onChange={(location) => {
|
|
413
|
+
setFieldTouched('baselineLocation', true);
|
|
414
|
+
setFieldValue('baselineLocation', location);
|
|
415
|
+
}}
|
|
416
|
+
onShowAllChange={(showAll) => this.setState({ showAllControlLocations: showAll })}
|
|
417
|
+
/>
|
|
418
|
+
</div>
|
|
419
|
+
</div>
|
|
420
|
+
<div className="form-group">
|
|
421
|
+
<div className="col-md-3 sm-label-right">
|
|
422
|
+
Canary <HelpField id="pipeline.config.canary.canaryGroup" />
|
|
423
|
+
</div>
|
|
424
|
+
<div className="col-md-7">
|
|
425
|
+
<ValidatedField errors={errors} touched={touched} name="canaryScope" />
|
|
426
|
+
</div>
|
|
427
|
+
</div>
|
|
428
|
+
<div className="form-group">
|
|
429
|
+
<div className="col-md-3 sm-label-right">
|
|
430
|
+
Canary Location <HelpField id="pipeline.config.canary.canaryLocation" />
|
|
431
|
+
</div>
|
|
432
|
+
<div className="col-md-7">
|
|
433
|
+
<LocationField
|
|
434
|
+
hasLocationChoices={hasLocationChoices}
|
|
435
|
+
showAll={showAllExperimentLocations}
|
|
436
|
+
recommendedLocations={recommendedLocations}
|
|
437
|
+
locations={locations}
|
|
438
|
+
value={values.canaryLocation}
|
|
439
|
+
field="canaryLocation"
|
|
440
|
+
errors={errors}
|
|
441
|
+
touched={touched}
|
|
442
|
+
onChange={(location) => {
|
|
443
|
+
setFieldTouched('canaryLocation', true);
|
|
444
|
+
setFieldValue('canaryLocation', location);
|
|
445
|
+
}}
|
|
446
|
+
onShowAllChange={(showAll) => this.setState({ showAllExperimentLocations: showAll })}
|
|
447
|
+
/>
|
|
448
|
+
</div>
|
|
449
|
+
</div>
|
|
450
|
+
<h5>Metric Scope</h5>
|
|
451
|
+
<div className="horizontal-rule" />
|
|
452
|
+
{RESOURCE_TYPES.hasOwnProperty(get(selectedMetricsStore, 'type')) && (
|
|
453
|
+
<div className="form-group">
|
|
454
|
+
<div className="col-md-3 sm-label-right">Resource Type</div>
|
|
455
|
+
<div className="col-md-7">
|
|
456
|
+
<TetheredSelect
|
|
457
|
+
value={values.resourceType}
|
|
458
|
+
options={RESOURCE_TYPES[selectedMetricsStore.type as keyof typeof RESOURCE_TYPES]}
|
|
459
|
+
onChange={(item: { label: string; value: string }) =>
|
|
460
|
+
setFieldValue('resourceType', (item && item.value) || '')
|
|
461
|
+
}
|
|
462
|
+
/>
|
|
463
|
+
<ErrorMessage field="resourceType" errors={errors} touched={touched} />
|
|
464
|
+
</div>
|
|
465
|
+
</div>
|
|
466
|
+
)}
|
|
467
|
+
<div className="form-group">
|
|
468
|
+
<div className="col-md-3 sm-label-right">
|
|
469
|
+
Extended Params <HelpField id="pipeline.config.canary.extendedScopeParams" />
|
|
470
|
+
</div>
|
|
471
|
+
<div className="col-md-7">
|
|
472
|
+
<MapEditor
|
|
473
|
+
model={values.extendedScopeParams}
|
|
474
|
+
onChange={(model) => setFieldValue('extendedScopeParams', model)}
|
|
475
|
+
hiddenKeys={['resourceType']}
|
|
476
|
+
/>
|
|
477
|
+
</div>
|
|
478
|
+
</div>
|
|
479
|
+
<h5>Scoring Thresholds</h5>
|
|
480
|
+
<div className="horizontal-rule" />
|
|
481
|
+
<CanaryScores
|
|
482
|
+
onChange={({ unhealthyScore, successfulScore }) => {
|
|
483
|
+
setFieldValue('marginalThreshold', unhealthyScore);
|
|
484
|
+
setFieldValue('passThreshold', successfulScore);
|
|
485
|
+
}}
|
|
486
|
+
successfulHelpFieldId="pipeline.config.canary.passingScore"
|
|
487
|
+
successfulLabel="Pass "
|
|
488
|
+
successfulScore={values.passThreshold}
|
|
489
|
+
unhealthyHelpFieldId="pipeline.config.canary.marginalScore"
|
|
490
|
+
unhealthyLabel="Marginal "
|
|
491
|
+
unhealthyScore={values.marginalThreshold}
|
|
492
|
+
/>
|
|
493
|
+
{showAdvancedSettings && (
|
|
494
|
+
<AdvancedSettings
|
|
495
|
+
metricsStores={metricsStores}
|
|
496
|
+
selectedMetricsStore={selectedMetricsStore}
|
|
497
|
+
objectStores={objectStores}
|
|
498
|
+
selectedObjectStore={selectedObjectStore}
|
|
499
|
+
onChange={setFieldValue}
|
|
500
|
+
errors={errors}
|
|
501
|
+
touched={touched}
|
|
502
|
+
/>
|
|
503
|
+
)}
|
|
504
|
+
</div>
|
|
505
|
+
</div>
|
|
506
|
+
</div>
|
|
507
|
+
{get(status, 'error') && (
|
|
508
|
+
<div className="row">
|
|
509
|
+
<div className="col-md-7 col-md-offset-3">
|
|
510
|
+
<div className="well-compact alert alert-danger">
|
|
511
|
+
<b>There was a problem starting your analysis:</b>
|
|
512
|
+
<span>{JSON.stringify(status.error.data)}</span>
|
|
513
|
+
</div>
|
|
514
|
+
</div>
|
|
515
|
+
</div>
|
|
516
|
+
)}
|
|
517
|
+
</Modal.Body>
|
|
518
|
+
<Modal.Footer className="horizontal right">
|
|
519
|
+
<button className="btn btn-default" onClick={dismissModal} type="button">
|
|
520
|
+
Cancel
|
|
521
|
+
</button>
|
|
522
|
+
<SubmitFormButton isValid={isValid} isSubmitting={isSubmitting} errors={errors} />
|
|
523
|
+
</Modal.Footer>
|
|
524
|
+
</Form>
|
|
525
|
+
);
|
|
526
|
+
}}
|
|
527
|
+
/>
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const SubmitFormButton = ({
|
|
533
|
+
errors,
|
|
534
|
+
isValid,
|
|
535
|
+
isSubmitting,
|
|
536
|
+
}: {
|
|
537
|
+
errors: FormikErrors<any>;
|
|
538
|
+
isValid: boolean;
|
|
539
|
+
isSubmitting: boolean;
|
|
540
|
+
}) => {
|
|
541
|
+
const button = (
|
|
542
|
+
<SubmitButton isDisabled={!isValid} submitting={isSubmitting} isFormSubmit={true} label="Start analysis" />
|
|
543
|
+
);
|
|
544
|
+
const errorMessages = Object.values(errors).map((error: string, i) => <li key={i}>{error}</li>);
|
|
545
|
+
if (isValid || !errorMessages.length) {
|
|
546
|
+
return button;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const popoverComponent = () => (
|
|
550
|
+
<div>
|
|
551
|
+
The following errors must be corrected before submitting:<ul>{errorMessages}</ul>
|
|
552
|
+
</div>
|
|
553
|
+
);
|
|
554
|
+
return (
|
|
555
|
+
<div className="sp-margin-s-left">
|
|
556
|
+
<HoverablePopover Component={popoverComponent}>{button}</HoverablePopover>
|
|
557
|
+
</div>
|
|
558
|
+
);
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
const combineLocations = (includeAll: boolean, recommendedLocations: string[], locations: string[]) => {
|
|
562
|
+
if (includeAll) {
|
|
563
|
+
return [...new Set(recommendedLocations.concat(locations))];
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
return recommendedLocations.length > 0 ? recommendedLocations : locations;
|
|
567
|
+
};
|
|
568
|
+
|
|
569
|
+
interface ILocationFieldProps {
|
|
570
|
+
hasLocationChoices: boolean;
|
|
571
|
+
showAll: boolean;
|
|
572
|
+
recommendedLocations: string[];
|
|
573
|
+
locations: string[];
|
|
574
|
+
value: string;
|
|
575
|
+
onChange: (location: string) => any;
|
|
576
|
+
onShowAllChange: (showAll: boolean) => any;
|
|
577
|
+
errors: FormikErrors<IManualAnalysisModalFormProps>;
|
|
578
|
+
touched: FormikTouched<IManualAnalysisModalFormProps>;
|
|
579
|
+
field: keyof IManualAnalysisModalFormProps;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
const LocationField = ({
|
|
583
|
+
hasLocationChoices,
|
|
584
|
+
showAll,
|
|
585
|
+
recommendedLocations,
|
|
586
|
+
locations,
|
|
587
|
+
value,
|
|
588
|
+
onChange,
|
|
589
|
+
onShowAllChange,
|
|
590
|
+
errors,
|
|
591
|
+
touched,
|
|
592
|
+
field,
|
|
593
|
+
}: ILocationFieldProps) => {
|
|
594
|
+
if (!hasLocationChoices) {
|
|
595
|
+
return <ValidatedField name={field} errors={errors} touched={touched} />;
|
|
596
|
+
}
|
|
597
|
+
const combinedLocations = combineLocations(showAll, recommendedLocations, locations).sort();
|
|
598
|
+
const options = combinedLocations.map((location) => ({ label: location, value: location }));
|
|
599
|
+
|
|
600
|
+
return (
|
|
601
|
+
<>
|
|
602
|
+
<TetheredSelect
|
|
603
|
+
clearable={false}
|
|
604
|
+
value={value}
|
|
605
|
+
options={options}
|
|
606
|
+
onChange={(item: { label: string; value: string }) => onChange((item && item.value) || '')}
|
|
607
|
+
/>
|
|
608
|
+
<ErrorMessage field={field} errors={errors} touched={touched} />
|
|
609
|
+
{recommendedLocations.length > 0 && locations.length > 0 && (
|
|
610
|
+
<div className="pull-right">
|
|
611
|
+
<button
|
|
612
|
+
type="button"
|
|
613
|
+
className="link"
|
|
614
|
+
onClick={() => {
|
|
615
|
+
onShowAllChange(!showAll);
|
|
616
|
+
if (!combineLocations(!showAll, recommendedLocations, locations).includes(value)) {
|
|
617
|
+
onChange('');
|
|
618
|
+
}
|
|
619
|
+
}}
|
|
620
|
+
>
|
|
621
|
+
{showAll ? 'Only show recommended locations' : 'Show all locations'}
|
|
622
|
+
</button>
|
|
623
|
+
</div>
|
|
624
|
+
)}
|
|
625
|
+
</>
|
|
626
|
+
);
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
interface IAdvancedSettingsProps {
|
|
630
|
+
metricsStores: IKayentaAccount[];
|
|
631
|
+
objectStores: IKayentaAccount[];
|
|
632
|
+
selectedMetricsStore: IKayentaAccount;
|
|
633
|
+
selectedObjectStore: IKayentaAccount;
|
|
634
|
+
onChange: (field: 'metricsAccountName' | 'storageAccountName', value: string) => any;
|
|
635
|
+
errors: FormikErrors<IManualAnalysisModalFormProps>;
|
|
636
|
+
touched: FormikTouched<IManualAnalysisModalFormProps>;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
const AdvancedSettings = ({
|
|
640
|
+
metricsStores,
|
|
641
|
+
selectedMetricsStore,
|
|
642
|
+
objectStores,
|
|
643
|
+
selectedObjectStore,
|
|
644
|
+
onChange,
|
|
645
|
+
errors,
|
|
646
|
+
touched,
|
|
647
|
+
}: IAdvancedSettingsProps) => {
|
|
648
|
+
return (
|
|
649
|
+
<>
|
|
650
|
+
<h5>Advanced Settings</h5>
|
|
651
|
+
<div className="horizontal-rule" />
|
|
652
|
+
<div className="form-group">
|
|
653
|
+
<div className="col-md-3 sm-label-right">
|
|
654
|
+
Metrics Account <HelpField id="pipeline.config.metricsAccount" />
|
|
655
|
+
</div>
|
|
656
|
+
<div className="col-md-7">
|
|
657
|
+
<TetheredSelect
|
|
658
|
+
clearable={false}
|
|
659
|
+
value={get(selectedMetricsStore, 'name')}
|
|
660
|
+
options={metricsStores.map(({ name }) => ({ label: name, value: name }))}
|
|
661
|
+
onChange={(item: { label: string; value: string }) =>
|
|
662
|
+
onChange('metricsAccountName', (item && item.value) || '')
|
|
663
|
+
}
|
|
664
|
+
/>
|
|
665
|
+
<ErrorMessage field="metricsAccountName" errors={errors} touched={touched} />
|
|
666
|
+
</div>
|
|
667
|
+
</div>
|
|
668
|
+
<div className="form-group">
|
|
669
|
+
<div className="col-md-3 sm-label-right">
|
|
670
|
+
Storage Account <HelpField id="pipeline.config.storageAccount" />
|
|
671
|
+
</div>
|
|
672
|
+
<div className="col-md-7">
|
|
673
|
+
<TetheredSelect
|
|
674
|
+
clearable={false}
|
|
675
|
+
value={get(selectedObjectStore, 'name')}
|
|
676
|
+
options={objectStores.map(({ name }) => ({ label: name, value: name }))}
|
|
677
|
+
onChange={(item: { label: string; value: string }) =>
|
|
678
|
+
onChange('storageAccountName', (item && item.value) || '')
|
|
679
|
+
}
|
|
680
|
+
/>
|
|
681
|
+
<ErrorMessage field="storageAccountName" errors={errors} touched={touched} />
|
|
682
|
+
</div>
|
|
683
|
+
</div>
|
|
684
|
+
</>
|
|
685
|
+
);
|
|
686
|
+
};
|
|
687
|
+
|
|
688
|
+
interface IErrorMessageProps {
|
|
689
|
+
field: keyof IManualAnalysisModalFormProps;
|
|
690
|
+
errors: FormikErrors<IManualAnalysisModalFormProps>;
|
|
691
|
+
touched: FormikTouched<IManualAnalysisModalFormProps>;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
const ErrorMessage = ({ field, errors, touched }: IErrorMessageProps) => {
|
|
695
|
+
if (!touched[field] || !errors[field]) {
|
|
696
|
+
return null;
|
|
697
|
+
}
|
|
698
|
+
return <div className="error-message">{errors[field]}</div>;
|
|
699
|
+
};
|
|
700
|
+
|
|
701
|
+
export interface IValidatedFieldProps {
|
|
702
|
+
errors: FormikErrors<IManualAnalysisModalFormProps>;
|
|
703
|
+
touched: FormikTouched<IManualAnalysisModalFormProps>;
|
|
704
|
+
name: keyof IManualAnalysisModalFormProps;
|
|
705
|
+
[field: string]: any;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
const ValidatedField = (props: IValidatedFieldProps) => {
|
|
709
|
+
const { errors, name, touched } = props;
|
|
710
|
+
return (
|
|
711
|
+
<>
|
|
712
|
+
<Field {...props} className="form-control input-sm" required={touched[name]} error={errors[name]} />
|
|
713
|
+
<ErrorMessage field={name} errors={errors} touched={touched} />
|
|
714
|
+
</>
|
|
715
|
+
);
|
|
716
|
+
};
|