@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,42 @@
|
|
|
1
|
+
@import './config.less';
|
|
2
|
+
|
|
3
|
+
.time-series {
|
|
4
|
+
.xyframe-matte {
|
|
5
|
+
fill: @background-color;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.zoom-icon {
|
|
9
|
+
bottom: 10px;
|
|
10
|
+
left: 30px;
|
|
11
|
+
position: absolute;
|
|
12
|
+
color: var(--color-dove-gray);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.tooltip-dual-axis-row {
|
|
16
|
+
margin-bottom: 10px;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.tooltip-ts {
|
|
20
|
+
font-weight: 600;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.axis-title.x.axis.bottom {
|
|
24
|
+
text {
|
|
25
|
+
transform: translateY(-8px);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.canary-dual-axis {
|
|
30
|
+
.axis-label,
|
|
31
|
+
.axis-title text {
|
|
32
|
+
fill: @canary-color;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.baseline-dual-axis {
|
|
37
|
+
.axis-label,
|
|
38
|
+
.axis-title text {
|
|
39
|
+
fill: @baseline-color;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
import classNames from 'classnames';
|
|
2
|
+
import { scaleUtc } from 'd3-scale';
|
|
3
|
+
import { curveStepAfter } from 'd3-shape';
|
|
4
|
+
import { IMetricSetPair } from 'kayenta/domain/IMetricSetPair';
|
|
5
|
+
import * as React from 'react';
|
|
6
|
+
import { IMinimapProps, IXYFrameHoverBaseArgs, IXYFrameProps, MinimapXYFrame, XYFrame } from 'semiotic';
|
|
7
|
+
|
|
8
|
+
import { SETTINGS, timestamp } from '@spinnaker/core';
|
|
9
|
+
|
|
10
|
+
import ChartHeader from './chartHeader';
|
|
11
|
+
import ChartLegend from './chartLegend';
|
|
12
|
+
import CircleIcon from './circleIcon';
|
|
13
|
+
import { vizConfig } from './config';
|
|
14
|
+
import CustomAxisTickLabel from './customAxisTickLabel';
|
|
15
|
+
import DifferenceArea from './differenceArea';
|
|
16
|
+
import SecondaryTSXAxis from './secondaryTSXAxis';
|
|
17
|
+
import { IMargin, ISemioticChartProps, ITooltip } from './semiotic.service';
|
|
18
|
+
import Tooltip from './tooltip';
|
|
19
|
+
import * as utils from './utils';
|
|
20
|
+
|
|
21
|
+
import './timeSeries.less';
|
|
22
|
+
|
|
23
|
+
const { defaultTimeZone } = SETTINGS;
|
|
24
|
+
|
|
25
|
+
interface IDataPoint {
|
|
26
|
+
value: number | null;
|
|
27
|
+
timestampMillisActual: number;
|
|
28
|
+
timestampMillisNormalizedMain: number;
|
|
29
|
+
timestampMillisNormalizedMinimap: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface IChartDataSet {
|
|
33
|
+
color: string;
|
|
34
|
+
label: string;
|
|
35
|
+
coordinates: IDataPoint[];
|
|
36
|
+
coordinatesUnfiltered: IDataPoint[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface IChartData {
|
|
40
|
+
dataSets: IChartDataSet[];
|
|
41
|
+
xExtentNormalizedMinimap: number[];
|
|
42
|
+
xExtentNormalizedMain: number[];
|
|
43
|
+
millisSetNormalizedMain: number[];
|
|
44
|
+
millisSetNormalizedMinimap: number[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface ITimeSeriesState {
|
|
48
|
+
tooltip: ITooltip;
|
|
49
|
+
userBrushExtent: Date[] | null;
|
|
50
|
+
showGroup: { [group: string]: boolean };
|
|
51
|
+
graphs: { [graph: string]: JSX.Element };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface ITooltipDataPoint {
|
|
55
|
+
color: string;
|
|
56
|
+
label: string;
|
|
57
|
+
value: number | undefined;
|
|
58
|
+
ts: number;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface IDataSetsAttributes {
|
|
62
|
+
startTimeMillisOffset: number;
|
|
63
|
+
maxDataCount: number;
|
|
64
|
+
shouldDisplayMinimap: boolean;
|
|
65
|
+
shouldUseSecondaryXAxis: boolean;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/*There are 3 vizualisations inside this component:
|
|
69
|
+
1. The main Timeseries chart
|
|
70
|
+
2. The minimap chart for users to zoom/ pan the main timeseries. Only show this if there are many data points
|
|
71
|
+
3. The difference chart that displays the diff between canary and baseline
|
|
72
|
+
*/
|
|
73
|
+
export default class TimeSeries extends React.Component<ISemioticChartProps, ITimeSeriesState> {
|
|
74
|
+
public state: ITimeSeriesState = {
|
|
75
|
+
tooltip: null,
|
|
76
|
+
userBrushExtent: null,
|
|
77
|
+
showGroup: {
|
|
78
|
+
baseline: true,
|
|
79
|
+
canary: true,
|
|
80
|
+
},
|
|
81
|
+
graphs: {
|
|
82
|
+
// a standalone timeseries chart if data points are few, otherwise it also includes a brushable/ zoomable minimap
|
|
83
|
+
line: null,
|
|
84
|
+
// graph that shows the delta between canary and baseline
|
|
85
|
+
differenceArea: null,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
private mainMinimapHeight = 320; // total height of the main & minimap (if applicable)
|
|
90
|
+
|
|
91
|
+
private marginMain: IMargin = {
|
|
92
|
+
top: 10,
|
|
93
|
+
left: 60,
|
|
94
|
+
right: 20,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
private marginMinimap: IMargin = {
|
|
98
|
+
top: 0,
|
|
99
|
+
bottom: 0,
|
|
100
|
+
left: 60,
|
|
101
|
+
right: 20,
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
public componentDidMount() {
|
|
105
|
+
this.createGraphs();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Only reconstruct the graph components when necessary
|
|
109
|
+
public componentDidUpdate(prevProps: ISemioticChartProps, prevState: ITimeSeriesState) {
|
|
110
|
+
const { metricSetPair, parentWidth } = this.props;
|
|
111
|
+
const { userBrushExtent, showGroup } = this.state;
|
|
112
|
+
if (
|
|
113
|
+
metricSetPair !== prevProps.metricSetPair ||
|
|
114
|
+
parentWidth !== prevProps.parentWidth ||
|
|
115
|
+
userBrushExtent !== prevState.userBrushExtent ||
|
|
116
|
+
showGroup !== prevState.showGroup
|
|
117
|
+
) {
|
|
118
|
+
this.createGraphs();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/*
|
|
123
|
+
* Generate chart Data
|
|
124
|
+
* In cases where the start Millis is different betwen canary and baseline, we want to:
|
|
125
|
+
* 1) If data point count is different, extend the shorter dataset to match the longer one (i.e. match the x extent)
|
|
126
|
+
* 2) normalize canary timestamp to match baseline timestamps (needed for the tooltip's
|
|
127
|
+
* voronoi overlay logic in semiotic to work properly)
|
|
128
|
+
*/
|
|
129
|
+
private getChartData = (dataSetsAttr: IDataSetsAttributes, metricSetPair: IMetricSetPair) => {
|
|
130
|
+
const { maxDataCount } = dataSetsAttr;
|
|
131
|
+
const { showGroup } = this.state;
|
|
132
|
+
const { dataGroupMap, colors } = vizConfig;
|
|
133
|
+
const { scopes, values } = metricSetPair;
|
|
134
|
+
const { userBrushExtent } = this.state;
|
|
135
|
+
const groups = ['baseline', 'canary'];
|
|
136
|
+
const isOnlyCanarySelected = showGroup.canary && !showGroup.baseline;
|
|
137
|
+
const stepMillis = scopes.control.stepMillis;
|
|
138
|
+
|
|
139
|
+
/*
|
|
140
|
+
* To support dual-axis, use 'normalized' ts supplied to semiotic for both canary and baseline.
|
|
141
|
+
* We store the actual ts as 'actuals' (which can be different from the normalized ts for canary).
|
|
142
|
+
*/
|
|
143
|
+
const dataSets: IChartDataSet[] = groups.map((g: string) => {
|
|
144
|
+
const dataPoints: IDataPoint[] = Array(maxDataCount)
|
|
145
|
+
.fill(0)
|
|
146
|
+
.map((_, i: number) => {
|
|
147
|
+
const name = dataGroupMap[g];
|
|
148
|
+
const timestampMillisActual = scopes[name].startTimeMillis + i * stepMillis;
|
|
149
|
+
|
|
150
|
+
// if only canary is selected, norm'd ts is based on canary's ts.
|
|
151
|
+
const timestampMillisNormalizedMain = isOnlyCanarySelected
|
|
152
|
+
? scopes[dataGroupMap['canary']].startTimeMillis + i * stepMillis
|
|
153
|
+
: scopes[dataGroupMap['baseline']].startTimeMillis + i * stepMillis;
|
|
154
|
+
|
|
155
|
+
// minimap always shows both data sets, so norm'd ts is always based on baseline's ts
|
|
156
|
+
const timestampMillisNormalizedMinimap = scopes[dataGroupMap['baseline']].startTimeMillis + i * stepMillis;
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
timestampMillisActual, // original ts for this data point
|
|
160
|
+
timestampMillisNormalizedMain,
|
|
161
|
+
timestampMillisNormalizedMinimap,
|
|
162
|
+
value: values[name][i],
|
|
163
|
+
};
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
label: g,
|
|
168
|
+
color: colors[g],
|
|
169
|
+
coordinates: dataPoints.filter((d) => typeof d.value === 'number'),
|
|
170
|
+
coordinatesUnfiltered: dataPoints,
|
|
171
|
+
};
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const millisSetNormalizedMinimap: number[] = dataSets[0].coordinatesUnfiltered.map(
|
|
175
|
+
(c: any) => c.timestampMillisNormalizedMinimap,
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
const millisSetNormalizedMain: number[] = (!userBrushExtent
|
|
179
|
+
? dataSets[0].coordinatesUnfiltered
|
|
180
|
+
: dataSets[0].coordinatesUnfiltered.filter((c: IDataPoint) => {
|
|
181
|
+
return (
|
|
182
|
+
c.timestampMillisNormalizedMinimap >= userBrushExtent[0].valueOf() &&
|
|
183
|
+
c.timestampMillisNormalizedMinimap <= userBrushExtent[1].valueOf()
|
|
184
|
+
);
|
|
185
|
+
})
|
|
186
|
+
).map((c: IDataPoint) => c.timestampMillisNormalizedMain);
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
dataSets,
|
|
190
|
+
millisSetNormalizedMain,
|
|
191
|
+
millisSetNormalizedMinimap,
|
|
192
|
+
xExtentNormalizedMain: [millisSetNormalizedMain[0], millisSetNormalizedMain[millisSetNormalizedMain.length - 1]],
|
|
193
|
+
xExtentNormalizedMinimap: [
|
|
194
|
+
millisSetNormalizedMinimap[0],
|
|
195
|
+
millisSetNormalizedMinimap[millisSetNormalizedMinimap.length - 1],
|
|
196
|
+
],
|
|
197
|
+
};
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// common chart props that are used for both the main XYFrame and Minimap (if applicable)
|
|
201
|
+
private createCommonChartProps = () => {
|
|
202
|
+
return {
|
|
203
|
+
lineType: {
|
|
204
|
+
type: 'line',
|
|
205
|
+
interpolator: curveStepAfter,
|
|
206
|
+
},
|
|
207
|
+
lineStyle: (ds: IChartDataSet) => ({
|
|
208
|
+
stroke: ds.color,
|
|
209
|
+
strokeWidth: 2,
|
|
210
|
+
strokeOpacity: 0.8,
|
|
211
|
+
}),
|
|
212
|
+
yAccessor: 'value',
|
|
213
|
+
xScaleType: scaleUtc(),
|
|
214
|
+
baseMarkProps: { transitionDuration: { default: 200, fill: 200 } },
|
|
215
|
+
};
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
private createLineChartProps = (
|
|
219
|
+
chartData: IChartData,
|
|
220
|
+
dataSetsAttributes: IDataSetsAttributes,
|
|
221
|
+
commonChartProps: IXYFrameProps<IChartDataSet, IDataPoint>,
|
|
222
|
+
) => {
|
|
223
|
+
const { axisTickLineHeight, axisTickLabelHeight, axisLabelHeight, minimapHeight } = vizConfig.timeSeries;
|
|
224
|
+
|
|
225
|
+
const { parentWidth } = this.props;
|
|
226
|
+
const { showGroup } = this.state;
|
|
227
|
+
|
|
228
|
+
const { shouldUseSecondaryXAxis, shouldDisplayMinimap } = dataSetsAttributes;
|
|
229
|
+
const { dataSets, xExtentNormalizedMain, xExtentNormalizedMinimap, millisSetNormalizedMain } = chartData;
|
|
230
|
+
|
|
231
|
+
// if secondary axis is needed, we need more bottom margin to fit both axes
|
|
232
|
+
const totalXAxisHeight = shouldUseSecondaryXAxis
|
|
233
|
+
? 2 * (axisTickLabelHeight + axisLabelHeight) + axisTickLineHeight
|
|
234
|
+
: axisTickLabelHeight;
|
|
235
|
+
|
|
236
|
+
const lineChartProps = {
|
|
237
|
+
...commonChartProps,
|
|
238
|
+
lines: dataSets.filter((ds: IChartDataSet) => showGroup[ds.label]), // only show selected groups
|
|
239
|
+
hoverAnnotation: [
|
|
240
|
+
{
|
|
241
|
+
type: 'x',
|
|
242
|
+
disable: ['connector', 'note'],
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
type: 'vertical-points',
|
|
246
|
+
threshold: 0.1,
|
|
247
|
+
r: () => 5,
|
|
248
|
+
},
|
|
249
|
+
],
|
|
250
|
+
customHoverBehavior: this.createChartHoverHandler(dataSets, dataSetsAttributes),
|
|
251
|
+
xExtent: xExtentNormalizedMain,
|
|
252
|
+
axes: [
|
|
253
|
+
{
|
|
254
|
+
orient: 'left',
|
|
255
|
+
label: 'metric value',
|
|
256
|
+
tickFormat: (d: number) => utils.formatMetricValue(d),
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
orient: 'bottom',
|
|
260
|
+
label: shouldUseSecondaryXAxis ? 'Baseline' : undefined,
|
|
261
|
+
tickValues: utils.calculateDateTimeTicks(millisSetNormalizedMain),
|
|
262
|
+
tickFormat: (d: number) => <CustomAxisTickLabel millis={d} />,
|
|
263
|
+
className: shouldUseSecondaryXAxis ? 'baseline-dual-axis' : '',
|
|
264
|
+
},
|
|
265
|
+
],
|
|
266
|
+
margin: { ...this.marginMain, bottom: totalXAxisHeight },
|
|
267
|
+
matte: true,
|
|
268
|
+
size: [parentWidth, shouldDisplayMinimap ? this.mainMinimapHeight - minimapHeight : this.mainMinimapHeight],
|
|
269
|
+
xAccessor: (d: IDataPoint) => new Date(d.timestampMillisNormalizedMain),
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
if (shouldDisplayMinimap) {
|
|
273
|
+
return {
|
|
274
|
+
...lineChartProps,
|
|
275
|
+
minimap: {
|
|
276
|
+
...commonChartProps,
|
|
277
|
+
lines: dataSets,
|
|
278
|
+
yBrushable: false,
|
|
279
|
+
brushEnd: this.onBrushEnd,
|
|
280
|
+
size: [parentWidth, minimapHeight],
|
|
281
|
+
axes: [
|
|
282
|
+
{
|
|
283
|
+
orient: 'left',
|
|
284
|
+
tickFormat: (): void => null,
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
orient: 'bottom',
|
|
288
|
+
},
|
|
289
|
+
],
|
|
290
|
+
margin: this.marginMinimap,
|
|
291
|
+
xAccessor: (d: IDataPoint) => new Date(d.timestampMillisNormalizedMinimap),
|
|
292
|
+
xExtent: xExtentNormalizedMinimap,
|
|
293
|
+
} as IMinimapProps<IChartDataSet, IDataPoint>,
|
|
294
|
+
};
|
|
295
|
+
} else {
|
|
296
|
+
return lineChartProps as IXYFrameProps<IChartDataSet, IDataPoint>;
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
// construct the graph JSX components and store them as states
|
|
301
|
+
private createGraphs = () => {
|
|
302
|
+
const { metricSetPair, parentWidth } = this.props;
|
|
303
|
+
const { showGroup } = this.state;
|
|
304
|
+
const { minimapDataPointsThreshold, minimapHeight } = vizConfig.timeSeries;
|
|
305
|
+
|
|
306
|
+
/*
|
|
307
|
+
* Generate the data needed for the graph components
|
|
308
|
+
*/
|
|
309
|
+
const baselineStartTimeMillis = metricSetPair.scopes.control.startTimeMillis;
|
|
310
|
+
const canaryStartTimeMillis = metricSetPair.scopes.experiment.startTimeMillis;
|
|
311
|
+
const isStartTimeMillisEqual = baselineStartTimeMillis === canaryStartTimeMillis;
|
|
312
|
+
|
|
313
|
+
// Top level attributes to determine how data is formatted & which chart components to include
|
|
314
|
+
const dataSetsAttributes = {
|
|
315
|
+
startTimeMillisOffset: canaryStartTimeMillis - baselineStartTimeMillis,
|
|
316
|
+
maxDataCount: Math.max(metricSetPair.values.control.length, metricSetPair.values.experiment.length),
|
|
317
|
+
shouldDisplayMinimap: metricSetPair.values.control.length > minimapDataPointsThreshold,
|
|
318
|
+
shouldUseSecondaryXAxis: !isStartTimeMillisEqual && showGroup.canary && showGroup.baseline,
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const chartData = this.getChartData(dataSetsAttributes, metricSetPair);
|
|
322
|
+
|
|
323
|
+
/*
|
|
324
|
+
* Build the visualization components
|
|
325
|
+
*/
|
|
326
|
+
const commonChartProps = this.createCommonChartProps();
|
|
327
|
+
const lineChartProps = this.createLineChartProps(chartData, dataSetsAttributes, commonChartProps);
|
|
328
|
+
|
|
329
|
+
const line = (
|
|
330
|
+
<>
|
|
331
|
+
<div className="time-series-chart">
|
|
332
|
+
{dataSetsAttributes.shouldDisplayMinimap ? (
|
|
333
|
+
<MinimapXYFrame {...lineChartProps} />
|
|
334
|
+
) : (
|
|
335
|
+
<XYFrame {...lineChartProps} />
|
|
336
|
+
)}
|
|
337
|
+
</div>
|
|
338
|
+
{dataSetsAttributes.shouldUseSecondaryXAxis ? (
|
|
339
|
+
<SecondaryTSXAxis
|
|
340
|
+
margin={{ left: this.marginMain.left, right: this.marginMain.right, top: 0, bottom: 0 }}
|
|
341
|
+
width={parentWidth}
|
|
342
|
+
millisSet={chartData.millisSetNormalizedMain.map(
|
|
343
|
+
(ms: number) => ms + dataSetsAttributes.startTimeMillisOffset,
|
|
344
|
+
)}
|
|
345
|
+
axisLabel="canary"
|
|
346
|
+
bottomOffset={dataSetsAttributes.shouldDisplayMinimap ? minimapHeight : 0}
|
|
347
|
+
/>
|
|
348
|
+
) : null}
|
|
349
|
+
{dataSetsAttributes.shouldDisplayMinimap ? (
|
|
350
|
+
<div className="zoom-icon">
|
|
351
|
+
<i className="fas fa-search-plus" />
|
|
352
|
+
</div>
|
|
353
|
+
) : null}
|
|
354
|
+
</>
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
const differenceArea =
|
|
358
|
+
showGroup.baseline && showGroup.canary ? (
|
|
359
|
+
<DifferenceArea {...this.props} millisSetBaseline={chartData.millisSetNormalizedMinimap} />
|
|
360
|
+
) : null;
|
|
361
|
+
|
|
362
|
+
this.setState({
|
|
363
|
+
graphs: {
|
|
364
|
+
line,
|
|
365
|
+
differenceArea,
|
|
366
|
+
},
|
|
367
|
+
});
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
private onLegendClickHandler = (group: string) => {
|
|
371
|
+
const showGroup = this.state.showGroup;
|
|
372
|
+
this.setState({
|
|
373
|
+
showGroup: { ...showGroup, [group]: !showGroup[group] },
|
|
374
|
+
});
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
// function factory to create a custom hover handler function based on the datasets
|
|
378
|
+
private createChartHoverHandler = (dataSets: IChartDataSet[], dataSetsAttr: IDataSetsAttributes) => {
|
|
379
|
+
const { shouldUseSecondaryXAxis } = dataSetsAttr;
|
|
380
|
+
return (d: (IXYFrameHoverBaseArgs<IDataPoint> & IDataPoint) | undefined) => {
|
|
381
|
+
if (d && d.timestampMillisNormalizedMain) {
|
|
382
|
+
const tooltipData = dataSets.map(
|
|
383
|
+
(ds: IChartDataSet): ITooltipDataPoint => {
|
|
384
|
+
const coord = ds.coordinatesUnfiltered.find(
|
|
385
|
+
(c: IDataPoint) => c.timestampMillisNormalizedMain === d.timestampMillisNormalizedMain,
|
|
386
|
+
);
|
|
387
|
+
return {
|
|
388
|
+
color: ds.color,
|
|
389
|
+
label: ds.label,
|
|
390
|
+
ts: coord.timestampMillisActual,
|
|
391
|
+
value: coord.value,
|
|
392
|
+
};
|
|
393
|
+
},
|
|
394
|
+
);
|
|
395
|
+
|
|
396
|
+
let tooltipRows = tooltipData
|
|
397
|
+
.concat()
|
|
398
|
+
.sort((a: ITooltipDataPoint, b: ITooltipDataPoint) => b.value - a.value)
|
|
399
|
+
.map((o: ITooltipDataPoint) => {
|
|
400
|
+
// if there's a ts offset, timestamp should be displayed for each group
|
|
401
|
+
const tsRow = shouldUseSecondaryXAxis ? <div className="tooltip-ts">{timestamp(o.ts)}</div> : null;
|
|
402
|
+
|
|
403
|
+
return (
|
|
404
|
+
<div key={o.label} className={classNames({ 'tooltip-dual-axis-row': shouldUseSecondaryXAxis })}>
|
|
405
|
+
{tsRow}
|
|
406
|
+
<div id={o.label}>
|
|
407
|
+
<CircleIcon group={o.label} />
|
|
408
|
+
<span>{`${o.label}: `}</span>
|
|
409
|
+
<span>{utils.formatMetricValue(o.value)}</span>
|
|
410
|
+
</div>
|
|
411
|
+
</div>
|
|
412
|
+
);
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
if (tooltipData.length === 2) {
|
|
416
|
+
const canaryMinusBaseline = tooltipData[1].value - tooltipData[0].value;
|
|
417
|
+
tooltipRows = tooltipRows.concat([
|
|
418
|
+
<div id="diff" key="diff" className="tooltip-row">
|
|
419
|
+
<span>Canary - Baseline: </span>
|
|
420
|
+
<span>{utils.formatMetricValue(canaryMinusBaseline)}</span>
|
|
421
|
+
</div>,
|
|
422
|
+
]);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const tooltipContent = (
|
|
426
|
+
<div>
|
|
427
|
+
{/* if no dual axes, display timestamp row at the top level */}
|
|
428
|
+
{shouldUseSecondaryXAxis ? null : (
|
|
429
|
+
<div key="ts" className="tooltip-ts">
|
|
430
|
+
{timestamp(tooltipData[0].ts)}
|
|
431
|
+
</div>
|
|
432
|
+
)}
|
|
433
|
+
{tooltipRows}
|
|
434
|
+
</div>
|
|
435
|
+
);
|
|
436
|
+
this.setState({
|
|
437
|
+
tooltip: {
|
|
438
|
+
content: tooltipContent,
|
|
439
|
+
x: d.voronoiX + this.marginMain.left,
|
|
440
|
+
y: d.voronoiY + this.marginMain.top,
|
|
441
|
+
},
|
|
442
|
+
});
|
|
443
|
+
} else {
|
|
444
|
+
this.setState({ tooltip: null });
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
// Handle user brush action event from semiotic
|
|
450
|
+
private onBrushEnd = (e: Date[]) => {
|
|
451
|
+
this.setState({
|
|
452
|
+
userBrushExtent: e,
|
|
453
|
+
});
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
public render() {
|
|
457
|
+
const { metricSetPair } = this.props;
|
|
458
|
+
const { showGroup, tooltip } = this.state;
|
|
459
|
+
const { line, differenceArea } = this.state.graphs;
|
|
460
|
+
|
|
461
|
+
return (
|
|
462
|
+
<div className="time-series">
|
|
463
|
+
<ChartHeader metric={metricSetPair.name} />
|
|
464
|
+
<ChartLegend showGroup={showGroup} isClickable={true} onClickHandler={this.onLegendClickHandler} />
|
|
465
|
+
<div className="graph-container">
|
|
466
|
+
{line}
|
|
467
|
+
<Tooltip {...tooltip} />
|
|
468
|
+
</div>
|
|
469
|
+
{differenceArea}
|
|
470
|
+
</div>
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import ReactTooltip from 'react-tooltip';
|
|
3
|
+
|
|
4
|
+
export interface ITooltipProps {
|
|
5
|
+
x?: number;
|
|
6
|
+
y?: number;
|
|
7
|
+
content?: JSX.Element;
|
|
8
|
+
// unique ids are needed if we want to show multiple tooltip objects at once
|
|
9
|
+
id?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/*
|
|
13
|
+
* Custom tooltip component to render on top of any svg component.
|
|
14
|
+
* It creates an invisible pixel and uses react-tooltip library
|
|
15
|
+
* to point to it.
|
|
16
|
+
*/
|
|
17
|
+
export default class Tooltip extends React.Component<ITooltipProps> {
|
|
18
|
+
private tooltipTarget: HTMLDivElement;
|
|
19
|
+
|
|
20
|
+
public componentDidUpdate(prevProps: ITooltipProps) {
|
|
21
|
+
const target = this.tooltipTarget;
|
|
22
|
+
if (prevProps.content && !this.props.content) {
|
|
23
|
+
ReactTooltip.hide(target);
|
|
24
|
+
} else if (this.props.content) {
|
|
25
|
+
ReactTooltip.show(target);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public render() {
|
|
30
|
+
const { x, y, content, id = 'tooltip' } = this.props;
|
|
31
|
+
const tooltipTargetStyle = {
|
|
32
|
+
left: x ? x : 0,
|
|
33
|
+
top: y ? y : 0,
|
|
34
|
+
height: 1,
|
|
35
|
+
width: 1,
|
|
36
|
+
opacity: 0,
|
|
37
|
+
pointerEvents: 'none',
|
|
38
|
+
position: 'absolute',
|
|
39
|
+
zIndex: 10,
|
|
40
|
+
} as React.CSSProperties;
|
|
41
|
+
|
|
42
|
+
const containerStyle = {
|
|
43
|
+
pointerEvents: 'none',
|
|
44
|
+
} as React.CSSProperties;
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div style={containerStyle}>
|
|
48
|
+
<div data-tip={true} data-for={id} style={tooltipTargetStyle} ref={(el) => (this.tooltipTarget = el)} />
|
|
49
|
+
<ReactTooltip id={id} type="light" border={true}>
|
|
50
|
+
{content ? content : null}
|
|
51
|
+
</ReactTooltip>
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { quantile } from 'd3-array';
|
|
2
|
+
import { format } from 'd3-format';
|
|
3
|
+
import { scaleUtc } from 'd3-scale';
|
|
4
|
+
import { DateTime } from 'luxon';
|
|
5
|
+
|
|
6
|
+
import { SETTINGS } from '@spinnaker/core';
|
|
7
|
+
|
|
8
|
+
import { ISummaryStatistics } from './semiotic.service';
|
|
9
|
+
|
|
10
|
+
/*
|
|
11
|
+
Formatter for any metric values. If the value is not a valid number (typically NaN),
|
|
12
|
+
we want to display "N/A" to highlight that there was no valid measurement.
|
|
13
|
+
Else, the format is made as close as possible to Atlas graph formats:
|
|
14
|
+
SI notation is used when the exponent is between -24 and 24 (inclusive),
|
|
15
|
+
and exponential notation otherwise. Both with 3 digits of precision
|
|
16
|
+
*/
|
|
17
|
+
export const formatMetricValue = (value: any) => {
|
|
18
|
+
if (typeof value !== 'number' || isNaN(value)) {
|
|
19
|
+
return 'N/A';
|
|
20
|
+
} else if (Math.abs(value) >= Math.pow(10, 25)) {
|
|
21
|
+
return format('-.3~e')(value);
|
|
22
|
+
} else {
|
|
23
|
+
return format('-.3~s')(value);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const fromMillis = (millis: number) => {
|
|
28
|
+
const luxonOpts = SETTINGS.feature.displayTimestampsInUserLocalTime ? undefined : { zone: SETTINGS.defaultTimeZone };
|
|
29
|
+
return DateTime.fromMillis(millis, luxonOpts);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/*
|
|
33
|
+
Formatter for timestamps. If the timestamp value is a date boundary (e.g. 04-01-2019 00:00:00),
|
|
34
|
+
an additional date label is returned, otherwise only a single label showing hour & minute is returned
|
|
35
|
+
*/
|
|
36
|
+
export const dateTimeTickFormatter = (d: number) => {
|
|
37
|
+
const datetime = fromMillis(d);
|
|
38
|
+
if (datetime.hour === 0 && datetime.minute === 0) {
|
|
39
|
+
return [datetime.toFormat('HH:mm'), datetime.toFormat('MMM DD')];
|
|
40
|
+
} else {
|
|
41
|
+
return [datetime.toFormat('HH:mm')];
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// function to choose the ideal tick values on the x-axis of a timeseries
|
|
46
|
+
// D3 has a smart tick generator, e.g. it'll tend to show midnight if the data crosses
|
|
47
|
+
// date boundaries. This way we can label date change as date, and other ticks as HH:mm
|
|
48
|
+
export const calculateDateTimeTicks = (millisSet: number[]) => {
|
|
49
|
+
const minMillis = millisSet[0];
|
|
50
|
+
const maxMillis = millisSet[millisSet.length - 1];
|
|
51
|
+
|
|
52
|
+
/*
|
|
53
|
+
* since d3 scale doesn't support custom timezone, we have to:
|
|
54
|
+
* 1)shift the UTC domain based on the tz,
|
|
55
|
+
* 2)have d3 calculate the ideal tick values as usual, and
|
|
56
|
+
* 3)shift back the result
|
|
57
|
+
*/
|
|
58
|
+
const offsetMillis = fromMillis(minMillis).offset * 60000;
|
|
59
|
+
const minMillisShifted = minMillis + offsetMillis;
|
|
60
|
+
const maxMillisShifted = maxMillis + offsetMillis;
|
|
61
|
+
const scale = scaleUtc().domain([new Date(minMillisShifted), new Date(maxMillisShifted)]);
|
|
62
|
+
const ticks = scale.ticks(6).map((d: Date) => d.valueOf() - offsetMillis);
|
|
63
|
+
return ticks;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const calculateSummaryStatistics = (values: number[]): ISummaryStatistics => {
|
|
67
|
+
const output = {
|
|
68
|
+
min: {
|
|
69
|
+
value: quantile(values, 0.0),
|
|
70
|
+
label: 'Minimum',
|
|
71
|
+
},
|
|
72
|
+
q1area: {
|
|
73
|
+
value: quantile(values, 0.25),
|
|
74
|
+
label: '25th %-ile',
|
|
75
|
+
},
|
|
76
|
+
median: {
|
|
77
|
+
value: quantile(values, 0.5),
|
|
78
|
+
label: 'Median',
|
|
79
|
+
},
|
|
80
|
+
q3area: {
|
|
81
|
+
value: quantile(values, 0.75),
|
|
82
|
+
label: '75th %-ile',
|
|
83
|
+
},
|
|
84
|
+
max: {
|
|
85
|
+
value: quantile(values, 1.0),
|
|
86
|
+
label: 'Maximum',
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
return output;
|
|
90
|
+
};
|