Commit 092445a4 authored by Clement Ho's avatar Clement Ho

Merge branch 'prettify-monitoring-code' into 'master'

Clean up monitoring components

See merge request gitlab-org/gitlab-ce!18030
parents d3520573 b95c1850
<script> <script>
import _ from 'underscore'; import _ from 'underscore';
import Flash from '../../flash'; import Flash from '../../flash';
import MonitoringService from '../services/monitoring_service'; import MonitoringService from '../services/monitoring_service';
import GraphGroup from './graph_group.vue'; import GraphGroup from './graph_group.vue';
import Graph from './graph.vue'; import Graph from './graph.vue';
import EmptyState from './empty_state.vue'; import EmptyState from './empty_state.vue';
import MonitoringStore from '../stores/monitoring_store'; import MonitoringStore from '../stores/monitoring_store';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
export default { export default {
components: { components: {
Graph, Graph,
GraphGroup, GraphGroup,
EmptyState, EmptyState,
},
props: {
hasMetrics: {
type: Boolean,
required: false,
default: true,
}, },
showLegend: {
props: { type: Boolean,
hasMetrics: { required: false,
type: Boolean, default: true,
required: false,
default: true,
},
showLegend: {
type: Boolean,
required: false,
default: true,
},
showPanels: {
type: Boolean,
required: false,
default: true,
},
forceSmallGraph: {
type: Boolean,
required: false,
default: false,
},
documentationPath: {
type: String,
required: true,
},
settingsPath: {
type: String,
required: true,
},
clustersPath: {
type: String,
required: true,
},
tagsPath: {
type: String,
required: true,
},
projectPath: {
type: String,
required: true,
},
metricsEndpoint: {
type: String,
required: true,
},
deploymentEndpoint: {
type: String,
required: false,
default: null,
},
emptyGettingStartedSvgPath: {
type: String,
required: true,
},
emptyLoadingSvgPath: {
type: String,
required: true,
},
emptyNoDataSvgPath: {
type: String,
required: true,
},
emptyUnableToConnectSvgPath: {
type: String,
required: true,
},
}, },
showPanels: {
data() { type: Boolean,
return { required: false,
store: new MonitoringStore(), default: true,
state: 'gettingStarted',
showEmptyState: true,
updateAspectRatio: false,
updatedAspectRatios: 0,
hoverData: {},
resizeThrottled: {},
};
}, },
forceSmallGraph: {
created() { type: Boolean,
this.service = new MonitoringService({ required: false,
metricsEndpoint: this.metricsEndpoint, default: false,
deploymentEndpoint: this.deploymentEndpoint,
});
eventHub.$on('toggleAspectRatio', this.toggleAspectRatio);
eventHub.$on('hoverChanged', this.hoverChanged);
}, },
documentationPath: {
beforeDestroy() { type: String,
eventHub.$off('toggleAspectRatio', this.toggleAspectRatio); required: true,
eventHub.$off('hoverChanged', this.hoverChanged);
window.removeEventListener('resize', this.resizeThrottled, false);
}, },
settingsPath: {
mounted() { type: String,
this.resizeThrottled = _.throttle(this.resize, 600); required: true,
if (!this.hasMetrics) { },
this.state = 'gettingStarted'; clustersPath: {
} else { type: String,
this.getGraphsData(); required: true,
window.addEventListener('resize', this.resizeThrottled, false); },
tagsPath: {
type: String,
required: true,
},
projectPath: {
type: String,
required: true,
},
metricsEndpoint: {
type: String,
required: true,
},
deploymentEndpoint: {
type: String,
required: false,
default: null,
},
emptyGettingStartedSvgPath: {
type: String,
required: true,
},
emptyLoadingSvgPath: {
type: String,
required: true,
},
emptyNoDataSvgPath: {
type: String,
required: true,
},
emptyUnableToConnectSvgPath: {
type: String,
required: true,
},
},
data() {
return {
store: new MonitoringStore(),
state: 'gettingStarted',
showEmptyState: true,
updateAspectRatio: false,
updatedAspectRatios: 0,
hoverData: {},
resizeThrottled: {},
};
},
created() {
this.service = new MonitoringService({
metricsEndpoint: this.metricsEndpoint,
deploymentEndpoint: this.deploymentEndpoint,
});
eventHub.$on('toggleAspectRatio', this.toggleAspectRatio);
eventHub.$on('hoverChanged', this.hoverChanged);
},
beforeDestroy() {
eventHub.$off('toggleAspectRatio', this.toggleAspectRatio);
eventHub.$off('hoverChanged', this.hoverChanged);
window.removeEventListener('resize', this.resizeThrottled, false);
},
mounted() {
this.resizeThrottled = _.throttle(this.resize, 600);
if (!this.hasMetrics) {
this.state = 'gettingStarted';
} else {
this.getGraphsData();
window.addEventListener('resize', this.resizeThrottled, false);
}
},
methods: {
getGraphsData() {
this.state = 'loading';
Promise.all([
this.service.getGraphsData().then(data => this.store.storeMetrics(data)),
this.service
.getDeploymentData()
.then(data => this.store.storeDeploymentData(data))
.catch(() => new Flash('Error getting deployment information.')),
])
.then(() => {
if (this.store.groups.length < 1) {
this.state = 'noData';
return;
}
this.showEmptyState = false;
})
.catch(() => {
this.state = 'unableToConnect';
});
},
resize() {
this.updateAspectRatio = true;
},
toggleAspectRatio() {
this.updatedAspectRatios = this.updatedAspectRatios += 1;
if (this.store.getMetricsCount() === this.updatedAspectRatios) {
this.updateAspectRatio = !this.updateAspectRatio;
this.updatedAspectRatios = 0;
} }
}, },
hoverChanged(data) {
methods: { this.hoverData = data;
getGraphsData() {
this.state = 'loading';
Promise.all([
this.service.getGraphsData()
.then(data => this.store.storeMetrics(data)),
this.service.getDeploymentData()
.then(data => this.store.storeDeploymentData(data))
.catch(() => new Flash('Error getting deployment information.')),
])
.then(() => {
if (this.store.groups.length < 1) {
this.state = 'noData';
return;
}
this.showEmptyState = false;
})
.catch(() => { this.state = 'unableToConnect'; });
},
resize() {
this.updateAspectRatio = true;
},
toggleAspectRatio() {
this.updatedAspectRatios = this.updatedAspectRatios += 1;
if (this.store.getMetricsCount() === this.updatedAspectRatios) {
this.updateAspectRatio = !this.updateAspectRatio;
this.updatedAspectRatios = 0;
}
},
hoverChanged(data) {
this.hoverData = data;
},
}, },
}; },
};
</script> </script>
<template> <template>
......
<script> <script>
export default { export default {
props: { props: {
documentationPath: { documentationPath: {
type: String, type: String,
required: true, required: true,
}, },
settingsPath: { settingsPath: {
type: String, type: String,
required: false, required: false,
default: '', default: '',
}, },
clustersPath: { clustersPath: {
type: String, type: String,
required: false, required: false,
default: '', default: '',
}, },
selectedState: { selectedState: {
type: String, type: String,
required: true, required: true,
},
emptyGettingStartedSvgPath: {
type: String,
required: true,
},
emptyLoadingSvgPath: {
type: String,
required: true,
},
emptyNoDataSvgPath: {
type: String,
required: true,
},
emptyUnableToConnectSvgPath: {
type: String,
required: true,
},
}, },
data() { emptyGettingStartedSvgPath: {
return { type: String,
states: { required: true,
gettingStarted: { },
svgUrl: this.emptyGettingStartedSvgPath, emptyLoadingSvgPath: {
title: 'Get started with performance monitoring', type: String,
description: `Stay updated about the performance and health required: true,
},
emptyNoDataSvgPath: {
type: String,
required: true,
},
emptyUnableToConnectSvgPath: {
type: String,
required: true,
},
},
data() {
return {
states: {
gettingStarted: {
svgUrl: this.emptyGettingStartedSvgPath,
title: 'Get started with performance monitoring',
description: `Stay updated about the performance and health
of your environment by configuring Prometheus to monitor your deployments.`, of your environment by configuring Prometheus to monitor your deployments.`,
buttonText: 'Install Prometheus on clusters', buttonText: 'Install Prometheus on clusters',
buttonPath: this.clustersPath, buttonPath: this.clustersPath,
secondaryButtonText: 'Configure existing Prometheus', secondaryButtonText: 'Configure existing Prometheus',
secondaryButtonPath: this.settingsPath, secondaryButtonPath: this.settingsPath,
}, },
loading: { loading: {
svgUrl: this.emptyLoadingSvgPath, svgUrl: this.emptyLoadingSvgPath,
title: 'Waiting for performance data', title: 'Waiting for performance data',
description: `Creating graphs uses the data from the Prometheus server. description: `Creating graphs uses the data from the Prometheus server.
If this takes a long time, ensure that data is available.`, If this takes a long time, ensure that data is available.`,
buttonText: 'View documentation', buttonText: 'View documentation',
buttonPath: this.documentationPath, buttonPath: this.documentationPath,
}, },
noData: { noData: {
svgUrl: this.emptyNoDataSvgPath, svgUrl: this.emptyNoDataSvgPath,
title: 'No data found', title: 'No data found',
description: `You are connected to the Prometheus server, but there is currently description: `You are connected to the Prometheus server, but there is currently
no data to display.`, no data to display.`,
buttonText: 'Configure Prometheus', buttonText: 'Configure Prometheus',
buttonPath: this.settingsPath, buttonPath: this.settingsPath,
}, },
unableToConnect: { unableToConnect: {
svgUrl: this.emptyUnableToConnectSvgPath, svgUrl: this.emptyUnableToConnectSvgPath,
title: 'Unable to connect to Prometheus server', title: 'Unable to connect to Prometheus server',
description: 'Ensure connectivity is available from the GitLab server to the ', description: 'Ensure connectivity is available from the GitLab server to the ',
buttonText: 'View documentation', buttonText: 'View documentation',
buttonPath: this.documentationPath, buttonPath: this.documentationPath,
},
}, },
};
},
computed: {
currentState() {
return this.states[this.selectedState];
},
showButtonDescription() {
if (this.selectedState === 'unableToConnect') return true;
return false;
}, },
};
},
computed: {
currentState() {
return this.states[this.selectedState];
},
showButtonDescription() {
if (this.selectedState === 'unableToConnect') return true;
return false;
}, },
}; },
};
</script> </script>
<template> <template>
......
<script> <script>
import { scaleLinear, scaleTime } from 'd3-scale'; import { scaleLinear, scaleTime } from 'd3-scale';
import { axisLeft, axisBottom } from 'd3-axis'; import { axisLeft, axisBottom } from 'd3-axis';
import { max, extent } from 'd3-array'; import { max, extent } from 'd3-array';
import { select } from 'd3-selection'; import { select } from 'd3-selection';
import GraphLegend from './graph/legend.vue'; import GraphLegend from './graph/legend.vue';
import GraphFlag from './graph/flag.vue'; import GraphFlag from './graph/flag.vue';
import GraphDeployment from './graph/deployment.vue'; import GraphDeployment from './graph/deployment.vue';
import GraphPath from './graph/path.vue'; import GraphPath from './graph/path.vue';
import MonitoringMixin from '../mixins/monitoring_mixins'; import MonitoringMixin from '../mixins/monitoring_mixins';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import measurements from '../utils/measurements'; import measurements from '../utils/measurements';
import { bisectDate, timeScaleFormat } from '../utils/date_time_formatters'; import { bisectDate, timeScaleFormat } from '../utils/date_time_formatters';
import createTimeSeries from '../utils/multiple_time_series'; import createTimeSeries from '../utils/multiple_time_series';
import bp from '../../breakpoints'; import bp from '../../breakpoints';
const d3 = { scaleLinear, scaleTime, axisLeft, axisBottom, max, extent, select }; const d3 = { scaleLinear, scaleTime, axisLeft, axisBottom, max, extent, select };
export default { export default {
components: { components: {
GraphLegend, GraphLegend,
GraphFlag, GraphFlag,
GraphDeployment, GraphDeployment,
GraphPath, GraphPath,
},
mixins: [MonitoringMixin],
props: {
graphData: {
type: Object,
required: true,
}, },
updateAspectRatio: {
mixins: [MonitoringMixin], type: Boolean,
required: true,
props: { },
graphData: { deploymentData: {
type: Object, type: Array,
required: true, required: true,
}, },
updateAspectRatio: { hoverData: {
type: Boolean, type: Object,
required: true, required: false,
}, default: () => ({}),
deploymentData: { },
type: Array, projectPath: {
required: true, type: String,
}, required: true,
hoverData: { },
type: Object, tagsPath: {
required: false, type: String,
default: () => ({}), required: true,
}, },
projectPath: { showLegend: {
type: String, type: Boolean,
required: true, required: false,
}, default: true,
tagsPath: { },
type: String, smallGraph: {
required: true, type: Boolean,
}, required: false,
showLegend: { default: false,
type: Boolean, },
required: false, },
default: true, data() {
}, return {
smallGraph: { baseGraphHeight: 450,
type: Boolean, baseGraphWidth: 600,
required: false, graphHeight: 450,
default: false, graphWidth: 600,
graphHeightOffset: 120,
margin: {},
unitOfDisplay: '',
yAxisLabel: '',
legendTitle: '',
reducedDeploymentData: [],
measurements: measurements.large,
currentData: {
time: new Date(),
value: 0,
}, },
currentDataIndex: 0,
currentXCoordinate: 0,
currentFlagPosition: 0,
showFlag: false,
showFlagContent: false,
timeSeries: [],
realPixelRatio: 1,
};
},
computed: {
outerViewBox() {
return `0 0 ${this.baseGraphWidth} ${this.baseGraphHeight}`;
}, },
innerViewBox() {
data() { return `0 0 ${this.baseGraphWidth - 150} ${this.baseGraphHeight}`;
},
axisTransform() {
return `translate(70, ${this.graphHeight - 100})`;
},
paddingBottomRootSvg() {
return { return {
baseGraphHeight: 450, paddingBottom: `${Math.ceil(this.baseGraphHeight * 100) / this.baseGraphWidth || 0}%`,
baseGraphWidth: 600,
graphHeight: 450,
graphWidth: 600,
graphHeightOffset: 120,
margin: {},
unitOfDisplay: '',
yAxisLabel: '',
legendTitle: '',
reducedDeploymentData: [],
measurements: measurements.large,
currentData: {
time: new Date(),
value: 0,
},
currentDataIndex: 0,
currentXCoordinate: 0,
currentFlagPosition: 0,
showFlag: false,
showFlagContent: false,
timeSeries: [],
realPixelRatio: 1,
}; };
}, },
deploymentFlagData() {
computed: { return this.reducedDeploymentData.find(deployment => deployment.showDeploymentFlag);
outerViewBox() {
return `0 0 ${this.baseGraphWidth} ${this.baseGraphHeight}`;
},
innerViewBox() {
return `0 0 ${this.baseGraphWidth - 150} ${this.baseGraphHeight}`;
},
axisTransform() {
return `translate(70, ${this.graphHeight - 100})`;
},
paddingBottomRootSvg() {
return {
paddingBottom: `${(Math.ceil(this.baseGraphHeight * 100) / this.baseGraphWidth) || 0}%`,
};
},
deploymentFlagData() {
return this.reducedDeploymentData.find(deployment => deployment.showDeploymentFlag);
},
}, },
},
watch: { watch: {
updateAspectRatio() { updateAspectRatio() {
if (this.updateAspectRatio) { if (this.updateAspectRatio) {
this.graphHeight = 450; this.graphHeight = 450;
this.graphWidth = 600; this.graphWidth = 600;
this.measurements = measurements.large; this.measurements = measurements.large;
this.draw(); this.draw();
eventHub.$emit('toggleAspectRatio'); eventHub.$emit('toggleAspectRatio');
} }
},
hoverData() {
this.positionFlag();
},
}, },
hoverData() {
mounted() { this.positionFlag();
this.draw();
}, },
},
mounted() {
this.draw();
},
methods: {
draw() {
const breakpointSize = bp.getBreakpointSize();
const query = this.graphData.queries[0];
this.margin = measurements.large.margin;
if (this.smallGraph || breakpointSize === 'xs' || breakpointSize === 'sm') {
this.graphHeight = 300;
this.margin = measurements.small.margin;
this.measurements = measurements.small;
}
this.unitOfDisplay = query.unit || '';
this.yAxisLabel = this.graphData.y_label || 'Values';
this.legendTitle = query.label || 'Average';
this.graphWidth = this.$refs.baseSvg.clientWidth - this.margin.left - this.margin.right;
this.graphHeight = this.graphHeight - this.margin.top - this.margin.bottom;
this.baseGraphHeight = this.graphHeight;
this.baseGraphWidth = this.graphWidth;
methods: { // pixel offsets inside the svg and outside are not 1:1
draw() { this.realPixelRatio = this.$refs.baseSvg.clientWidth / this.baseGraphWidth;
const breakpointSize = bp.getBreakpointSize();
const query = this.graphData.queries[0];
this.margin = measurements.large.margin;
if (this.smallGraph || breakpointSize === 'xs' || breakpointSize === 'sm') {
this.graphHeight = 300;
this.margin = measurements.small.margin;
this.measurements = measurements.small;
}
this.unitOfDisplay = query.unit || '';
this.yAxisLabel = this.graphData.y_label || 'Values';
this.legendTitle = query.label || 'Average';
this.graphWidth = this.$refs.baseSvg.clientWidth -
this.margin.left - this.margin.right;
this.graphHeight = this.graphHeight - this.margin.top - this.margin.bottom;
this.baseGraphHeight = this.graphHeight;
this.baseGraphWidth = this.graphWidth;
// pixel offsets inside the svg and outside are not 1:1
this.realPixelRatio = (this.$refs.baseSvg.clientWidth / this.baseGraphWidth);
this.renderAxesPaths();
this.formatDeployments();
},
handleMouseOverGraph(e) {
let point = this.$refs.graphData.createSVGPoint();
point.x = e.clientX;
point.y = e.clientY;
point = point.matrixTransform(this.$refs.graphData.getScreenCTM().inverse());
point.x = point.x += 7;
const firstTimeSeries = this.timeSeries[0];
const timeValueOverlay = firstTimeSeries.timeSeriesScaleX.invert(point.x);
const overlayIndex = bisectDate(firstTimeSeries.values, timeValueOverlay, 1);
const d0 = firstTimeSeries.values[overlayIndex - 1];
const d1 = firstTimeSeries.values[overlayIndex];
if (d0 === undefined || d1 === undefined) return;
const evalTime = timeValueOverlay - d0[0] > d1[0] - timeValueOverlay;
const hoveredDataIndex = evalTime ? overlayIndex : (overlayIndex - 1);
const hoveredDate = firstTimeSeries.values[hoveredDataIndex].time;
const currentDeployXPos = this.mouseOverDeployInfo(point.x);
eventHub.$emit('hoverChanged', { this.renderAxesPaths();
hoveredDate, this.formatDeployments();
currentDeployXPos, },
}); handleMouseOverGraph(e) {
}, let point = this.$refs.graphData.createSVGPoint();
point.x = e.clientX;
point.y = e.clientY;
point = point.matrixTransform(this.$refs.graphData.getScreenCTM().inverse());
point.x = point.x += 7;
const firstTimeSeries = this.timeSeries[0];
const timeValueOverlay = firstTimeSeries.timeSeriesScaleX.invert(point.x);
const overlayIndex = bisectDate(firstTimeSeries.values, timeValueOverlay, 1);
const d0 = firstTimeSeries.values[overlayIndex - 1];
const d1 = firstTimeSeries.values[overlayIndex];
if (d0 === undefined || d1 === undefined) return;
const evalTime = timeValueOverlay - d0[0] > d1[0] - timeValueOverlay;
const hoveredDataIndex = evalTime ? overlayIndex : overlayIndex - 1;
const hoveredDate = firstTimeSeries.values[hoveredDataIndex].time;
const currentDeployXPos = this.mouseOverDeployInfo(point.x);
renderAxesPaths() { eventHub.$emit('hoverChanged', {
this.timeSeries = createTimeSeries( hoveredDate,
this.graphData.queries, currentDeployXPos,
this.graphWidth, });
this.graphHeight, },
this.graphHeightOffset, renderAxesPaths() {
); this.timeSeries = createTimeSeries(
this.graphData.queries,
this.graphWidth,
this.graphHeight,
this.graphHeightOffset,
);
if (!this.showLegend) { if (!this.showLegend) {
this.baseGraphHeight -= 50; this.baseGraphHeight -= 50;
} else if (this.timeSeries.length > 3) { } else if (this.timeSeries.length > 3) {
this.baseGraphHeight = this.baseGraphHeight += (this.timeSeries.length - 3) * 20; this.baseGraphHeight = this.baseGraphHeight += (this.timeSeries.length - 3) * 20;
} }
const axisXScale = d3.scaleTime() const axisXScale = d3.scaleTime().range([0, this.graphWidth - 70]);
.range([0, this.graphWidth - 70]); const axisYScale = d3.scaleLinear().range([this.graphHeight - this.graphHeightOffset, 0]);
const axisYScale = d3.scaleLinear()
.range([this.graphHeight - this.graphHeightOffset, 0]);
const allValues = this.timeSeries.reduce((all, { values }) => all.concat(values), []); const allValues = this.timeSeries.reduce((all, { values }) => all.concat(values), []);
axisXScale.domain(d3.extent(allValues, d => d.time)); axisXScale.domain(d3.extent(allValues, d => d.time));
axisYScale.domain([0, d3.max(allValues.map(d => d.value))]); axisYScale.domain([0, d3.max(allValues.map(d => d.value))]);
const xAxis = d3.axisBottom() const xAxis = d3
.scale(axisXScale) .axisBottom()
.ticks(this.graphWidth / 120) .scale(axisXScale)
.tickFormat(timeScaleFormat); .ticks(this.graphWidth / 120)
.tickFormat(timeScaleFormat);
const yAxis = d3.axisLeft() const yAxis = d3
.scale(axisYScale) .axisLeft()
.ticks(measurements.yTicks); .scale(axisYScale)
.ticks(measurements.yTicks);
d3.select(this.$refs.baseSvg).select('.x-axis').call(xAxis); d3
.select(this.$refs.baseSvg)
.select('.x-axis')
.call(xAxis);
const width = this.graphWidth; const width = this.graphWidth;
d3.select(this.$refs.baseSvg).select('.y-axis').call(yAxis) d3
.selectAll('.tick') .select(this.$refs.baseSvg)
.each(function createTickLines(d, i) { .select('.y-axis')
if (i > 0) { .call(yAxis)
d3.select(this).select('line') .selectAll('.tick')
.attr('x2', width) .each(function createTickLines(d, i) {
.attr('class', 'axis-tick'); if (i > 0) {
} // Avoid adding the class to the first tick, to prevent coloring d3
}); // This will select all of the ticks once they're rendered .select(this)
}, .select('line')
.attr('x2', width)
.attr('class', 'axis-tick');
} // Avoid adding the class to the first tick, to prevent coloring
}); // This will select all of the ticks once they're rendered
}, },
}; },
};
</script> </script>
<template> <template>
......
<script> <script>
export default { export default {
props: { props: {
deploymentData: { deploymentData: {
type: Array, type: Array,
required: true, required: true,
},
graphHeight: {
type: Number,
required: true,
},
graphHeightOffset: {
type: Number,
required: true,
},
}, },
graphHeight: {
computed: { type: Number,
calculatedHeight() { required: true,
return this.graphHeight - this.graphHeightOffset;
},
}, },
graphHeightOffset: {
methods: { type: Number,
transformDeploymentGroup(deployment) { required: true,
return `translate(${Math.floor(deployment.xPos) - 5}, 20)`;
},
}, },
}; },
computed: {
calculatedHeight() {
return this.graphHeight - this.graphHeightOffset;
},
},
methods: {
transformDeploymentGroup(deployment) {
return `translate(${Math.floor(deployment.xPos) - 5}, 20)`;
},
},
};
</script> </script>
<template> <template>
<g class="deploy-info"> <g class="deploy-info">
......
<script> <script>
import { dateFormat, timeFormat } from '../../utils/date_time_formatters'; import { dateFormat, timeFormat } from '../../utils/date_time_formatters';
import { formatRelevantDigits } from '../../../lib/utils/number_utils'; import { formatRelevantDigits } from '../../../lib/utils/number_utils';
import icon from '../../../vue_shared/components/icon.vue'; import icon from '../../../vue_shared/components/icon.vue';
export default { export default {
components: { components: {
icon, icon,
}, },
props: { props: {
currentXCoordinate: { currentXCoordinate: {
type: Number, type: Number,
required: true, required: true,
},
currentData: {
type: Object,
required: true,
},
deploymentFlagData: {
type: Object,
required: false,
default: null,
},
graphHeight: {
type: Number,
required: true,
},
graphHeightOffset: {
type: Number,
required: true,
},
realPixelRatio: {
type: Number,
required: true,
},
showFlagContent: {
type: Boolean,
required: true,
},
timeSeries: {
type: Array,
required: true,
},
unitOfDisplay: {
type: String,
required: true,
},
currentDataIndex: {
type: Number,
required: true,
},
legendTitle: {
type: String,
required: true,
},
}, },
currentData: {
computed: { type: Object,
formatTime() { required: true,
return this.deploymentFlagData ?
timeFormat(this.deploymentFlagData.time) :
timeFormat(this.currentData.time);
},
formatDate() {
return this.deploymentFlagData ?
dateFormat(this.deploymentFlagData.time) :
dateFormat(this.currentData.time);
},
cursorStyle() {
const xCoordinate = this.deploymentFlagData ?
this.deploymentFlagData.xPos :
this.currentXCoordinate;
const offsetTop = 20 * this.realPixelRatio;
const offsetLeft = (70 + xCoordinate) * this.realPixelRatio;
const height = (this.graphHeight - this.graphHeightOffset) * this.realPixelRatio;
return {
top: `${offsetTop}px`,
left: `${offsetLeft}px`,
height: `${height}px`,
};
},
flagOrientation() {
if (this.currentXCoordinate * this.realPixelRatio > 120) {
return 'left';
}
return 'right';
},
}, },
deploymentFlagData: {
type: Object,
required: false,
default: null,
},
graphHeight: {
type: Number,
required: true,
},
graphHeightOffset: {
type: Number,
required: true,
},
realPixelRatio: {
type: Number,
required: true,
},
showFlagContent: {
type: Boolean,
required: true,
},
timeSeries: {
type: Array,
required: true,
},
unitOfDisplay: {
type: String,
required: true,
},
currentDataIndex: {
type: Number,
required: true,
},
legendTitle: {
type: String,
required: true,
},
},
computed: {
formatTime() {
return this.deploymentFlagData
? timeFormat(this.deploymentFlagData.time)
: timeFormat(this.currentData.time);
},
formatDate() {
return this.deploymentFlagData
? dateFormat(this.deploymentFlagData.time)
: dateFormat(this.currentData.time);
},
cursorStyle() {
const xCoordinate = this.deploymentFlagData
? this.deploymentFlagData.xPos
: this.currentXCoordinate;
methods: { const offsetTop = 20 * this.realPixelRatio;
seriesMetricValue(series) { const offsetLeft = (70 + xCoordinate) * this.realPixelRatio;
const index = this.deploymentFlagData ? const height = (this.graphHeight - this.graphHeightOffset) * this.realPixelRatio;
this.deploymentFlagData.seriesIndex :
this.currentDataIndex;
const value = series.values[index] &&
series.values[index].value;
if (isNaN(value)) {
return '-';
}
return `${formatRelevantDigits(value)}${this.unitOfDisplay}`;
},
seriesMetricLabel(index, series) {
if (this.timeSeries.length < 2) {
return this.legendTitle;
}
if (series.metricTag) {
return series.metricTag;
}
return `series ${index + 1}`;
},
strokeDashArray(type) { return {
if (type === 'dashed') return '6, 3'; top: `${offsetTop}px`,
if (type === 'dotted') return '3, 3'; left: `${offsetLeft}px`,
return null; height: `${height}px`,
}, };
},
flagOrientation() {
if (this.currentXCoordinate * this.realPixelRatio > 120) {
return 'left';
}
return 'right';
},
},
methods: {
seriesMetricValue(series) {
const index = this.deploymentFlagData
? this.deploymentFlagData.seriesIndex
: this.currentDataIndex;
const value = series.values[index] && series.values[index].value;
if (isNaN(value)) {
return '-';
}
return `${formatRelevantDigits(value)}${this.unitOfDisplay}`;
},
seriesMetricLabel(index, series) {
if (this.timeSeries.length < 2) {
return this.legendTitle;
}
if (series.metricTag) {
return series.metricTag;
}
return `series ${index + 1}`;
},
strokeDashArray(type) {
if (type === 'dashed') return '6, 3';
if (type === 'dotted') return '3, 3';
return null;
}, },
}; },
};
</script> </script>
<template> <template>
......
<script> <script>
import { formatRelevantDigits } from '../../../lib/utils/number_utils'; import { formatRelevantDigits } from '../../../lib/utils/number_utils';
export default {
props: {
graphWidth: {
type: Number,
required: true,
},
graphHeight: {
type: Number,
required: true,
},
margin: {
type: Object,
required: true,
},
measurements: {
type: Object,
required: true,
},
legendTitle: {
type: String,
required: true,
},
yAxisLabel: {
type: String,
required: true,
},
timeSeries: {
type: Array,
required: true,
},
unitOfDisplay: {
type: String,
required: true,
},
currentDataIndex: {
type: Number,
required: true,
},
showLegendGroup: {
type: Boolean,
required: false,
default: true,
},
},
data() {
return {
yLabelWidth: 0,
yLabelHeight: 0,
seriesXPosition: 0,
metricUsageXPosition: 0,
};
},
computed: {
textTransform() {
const yCoordinate = (((this.graphHeight - this.margin.top)
+ this.measurements.axisLabelLineOffset) / 2) || 0;
return `translate(15, ${yCoordinate}) rotate(-90)`;
},
rectTransform() {
const yCoordinate = (((this.graphHeight - this.margin.top)
+ this.measurements.axisLabelLineOffset) / 2)
+ (this.yLabelWidth / 2) || 0;
return `translate(0, ${yCoordinate}) rotate(-90)`;
},
xPosition() {
return (((this.graphWidth + this.measurements.axisLabelLineOffset) / 2)
- this.margin.right) || 0;
},
yPosition() {
return ((this.graphHeight - this.margin.top) + this.measurements.axisLabelLineOffset) || 0;
},
export default {
props: {
graphWidth: {
type: Number,
required: true,
}, },
mounted() { graphHeight: {
this.$nextTick(() => { type: Number,
const bbox = this.$refs.ylabel.getBBox(); required: true,
this.metricUsageXPosition = 0; },
this.seriesXPosition = 0; margin: {
if (this.$refs.legendTitleSvg != null) { type: Object,
this.seriesXPosition = this.$refs.legendTitleSvg[0].getBBox().width; required: true,
} },
if (this.$refs.seriesTitleSvg != null) { measurements: {
this.metricUsageXPosition = this.$refs.seriesTitleSvg[0].getBBox().width; type: Object,
} required: true,
this.yLabelWidth = bbox.width + 10; // Added some padding },
this.yLabelHeight = bbox.height + 5; legendTitle: {
}); type: String,
}, required: true,
methods: { },
translateLegendGroup(index) { yAxisLabel: {
return `translate(0, ${12 * (index)})`; type: String,
}, required: true,
},
formatMetricUsage(series) { timeSeries: {
const value = series.values[this.currentDataIndex] && type: Array,
series.values[this.currentDataIndex].value; required: true,
if (isNaN(value)) { },
return '-'; unitOfDisplay: {
} type: String,
return `${formatRelevantDigits(value)} ${this.unitOfDisplay}`; required: true,
}, },
currentDataIndex: {
type: Number,
required: true,
},
showLegendGroup: {
type: Boolean,
required: false,
default: true,
},
},
data() {
return {
yLabelWidth: 0,
yLabelHeight: 0,
seriesXPosition: 0,
metricUsageXPosition: 0,
};
},
computed: {
textTransform() {
const yCoordinate =
(this.graphHeight - this.margin.top + this.measurements.axisLabelLineOffset) / 2 || 0;
createSeriesString(index, series) { return `translate(15, ${yCoordinate}) rotate(-90)`;
if (series.metricTag) { },
return `${series.metricTag} ${this.formatMetricUsage(series)}`; rectTransform() {
} const yCoordinate =
return `${this.legendTitle} series ${index + 1} ${this.formatMetricUsage(series)}`; (this.graphHeight - this.margin.top + this.measurements.axisLabelLineOffset) / 2 +
}, this.yLabelWidth / 2 || 0;
strokeDashArray(type) { return `translate(0, ${yCoordinate}) rotate(-90)`;
if (type === 'dashed') return '6, 3'; },
if (type === 'dotted') return '3, 3'; xPosition() {
return null; return (this.graphWidth + this.measurements.axisLabelLineOffset) / 2 - this.margin.right || 0;
}, },
yPosition() {
return this.graphHeight - this.margin.top + this.measurements.axisLabelLineOffset || 0;
},
},
mounted() {
this.$nextTick(() => {
const bbox = this.$refs.ylabel.getBBox();
this.metricUsageXPosition = 0;
this.seriesXPosition = 0;
if (this.$refs.legendTitleSvg != null) {
this.seriesXPosition = this.$refs.legendTitleSvg[0].getBBox().width;
}
if (this.$refs.seriesTitleSvg != null) {
this.metricUsageXPosition = this.$refs.seriesTitleSvg[0].getBBox().width;
}
this.yLabelWidth = bbox.width + 10; // Added some padding
this.yLabelHeight = bbox.height + 5;
});
},
methods: {
translateLegendGroup(index) {
return `translate(0, ${12 * index})`;
},
formatMetricUsage(series) {
const value =
series.values[this.currentDataIndex] && series.values[this.currentDataIndex].value;
if (isNaN(value)) {
return '-';
}
return `${formatRelevantDigits(value)} ${this.unitOfDisplay}`;
},
createSeriesString(index, series) {
if (series.metricTag) {
return `${series.metricTag} ${this.formatMetricUsage(series)}`;
}
return `${this.legendTitle} series ${index + 1} ${this.formatMetricUsage(series)}`;
},
strokeDashArray(type) {
if (type === 'dashed') return '6, 3';
if (type === 'dotted') return '3, 3';
return null;
}, },
}; },
};
</script> </script>
<template> <template>
<g class="axis-label-container"> <g class="axis-label-container">
......
<script> <script>
export default { export default {
props: { props: {
generatedLinePath: { generatedLinePath: {
type: String, type: String,
required: true, required: true,
},
generatedAreaPath: {
type: String,
required: true,
},
lineStyle: {
type: String,
required: false,
default: '',
},
lineColor: {
type: String,
required: true,
},
areaColor: {
type: String,
required: true,
},
}, },
computed: { generatedAreaPath: {
strokeDashArray() { type: String,
if (this.lineStyle === 'dashed') return '3, 1'; required: true,
if (this.lineStyle === 'dotted') return '1, 1';
return null;
},
}, },
}; lineStyle: {
type: String,
required: false,
default: '',
},
lineColor: {
type: String,
required: true,
},
areaColor: {
type: String,
required: true,
},
},
computed: {
strokeDashArray() {
if (this.lineStyle === 'dashed') return '3, 1';
if (this.lineStyle === 'dotted') return '1, 1';
return null;
},
},
};
</script> </script>
<template> <template>
<g> <g>
......
<script> <script>
export default { export default {
props: { props: {
name: { name: {
type: String, type: String,
required: true, required: true,
},
showPanels: {
type: Boolean,
required: false,
default: true,
},
}, },
}; showPanels: {
type: Boolean,
required: false,
default: true,
},
},
};
</script> </script>
<template> <template>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment