diff --git a/app/assets/javascripts/monitoring/components/graph.vue b/app/assets/javascripts/monitoring/components/graph.vue index eede04a06cd61f61d61b85e684c84071e00f4178..a50b80c23d0534b75dd5d6da446d4d9dbb298377 100644 --- a/app/assets/javascripts/monitoring/components/graph.vue +++ b/app/assets/javascripts/monitoring/components/graph.vue @@ -69,8 +69,8 @@ currentFlagPosition: 0, showFlag: false, showFlagContent: false, - showDeployInfo: true, timeSeries: [], + realPixelRatio: 1, }; }, @@ -87,10 +87,7 @@ }, innerViewBox() { - if ((this.baseGraphWidth - 150) > 0) { - return `0 0 ${this.baseGraphWidth - 150} ${this.baseGraphHeight}`; - } - return '0 0 0 0'; + return `0 0 ${this.baseGraphWidth - 150} ${this.baseGraphHeight}`; }, axisTransform() { @@ -102,6 +99,10 @@ paddingBottom: `${(Math.ceil(this.baseGraphHeight * 100) / this.baseGraphWidth) || 0}%`, }; }, + + deploymentFlagData() { + return this.reducedDeploymentData.find(deployment => deployment.showDeploymentFlag); + }, }, methods: { @@ -122,6 +123,10 @@ 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(); }, @@ -261,6 +266,11 @@ :line-color="path.lineColor" :area-color="path.areaColor" /> + - - + diff --git a/app/assets/javascripts/monitoring/components/graph/deployment.vue b/app/assets/javascripts/monitoring/components/graph/deployment.vue index 026e2fd0c49e90efed32c29941c31c9d830f39ec..8d6393d4ce5611a5746eacbdae8324e495214d06 100644 --- a/app/assets/javascripts/monitoring/components/graph/deployment.vue +++ b/app/assets/javascripts/monitoring/components/graph/deployment.vue @@ -1,13 +1,6 @@ diff --git a/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js b/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js index cbca14ede026530d7062846d4a6eaca9b7092eff..6cc67ba57eec0d33dfd668f6131048d9239f8add 100644 --- a/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js +++ b/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js @@ -29,15 +29,18 @@ const mixins = { time.setSeconds(this.timeSeries[0].values[0].time.getSeconds()); if (xPos >= 0) { + const seriesIndex = bisectDate(this.timeSeries[0].values, time, 1); + deploymentDataArray.push({ id: deployment.id, time, sha: deployment.sha, commitUrl: `${this.projectPath}/commit/${deployment.sha}`, tag: deployment.tag, - tagUrl: `${this.tagsPath}/${deployment.tag}`, + tagUrl: deployment.tag ? `${this.tagsPath}/${deployment.ref.name}` : null, ref: deployment.ref.name, xPos, + seriesIndex, showDeploymentFlag: false, }); } diff --git a/app/assets/javascripts/monitoring/utils/date_time_formatters.js b/app/assets/javascripts/monitoring/utils/date_time_formatters.js index 068813ddee6fc0c35bb8ed8dc78ba8401b504901..f3c9acdd93ed11f1f1c366656001c6940a11bc9a 100644 --- a/app/assets/javascripts/monitoring/utils/date_time_formatters.js +++ b/app/assets/javascripts/monitoring/utils/date_time_formatters.js @@ -14,7 +14,7 @@ const d3 = { timeYear, }; -export const dateFormat = d3.time('%b %-d, %Y'); +export const dateFormat = d3.time('%a, %b %-d'); export const timeFormat = d3.time('%-I:%M%p'); export const dateFormatWithName = d3.time('%a, %b %-d'); export const bisectDate = d3.bisector(d => d.time).left; diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index f4882305c57a13452c4f38f0e2b55ff8f537693b..794bc668562e208ea19ccb287dba013835f4bd4b 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -248,6 +248,73 @@ } } +.prometheus-graph-cursor { + position: absolute; + background: $theme-gray-600; + width: 1px; +} + +.prometheus-graph-flag { + display: block; + min-width: 160px; + + h5 { + padding: 0; + margin: 0; + font-size: 14px; + line-height: 1.2; + } + + table { + border-collapse: collapse; + padding: 0; + margin: 0; + } + + td { + vertical-align: middle; + + + td { + padding-left: 5px; + vertical-align: top; + } + } + + .deploy-meta-content { + border-bottom: 1px solid $white-dark; + + svg { + height: 15px; + vertical-align: bottom; + } + } + + &.popover { + &.left { + left: auto; + right: 0; + margin-right: 10px; + } + + &.right { + left: 0; + right: auto; + margin-left: 10px; + } + + > .arrow { + top: 40px; + } + + > .popover-title, + > .popover-content { + padding: 5px 8px; + font-size: 12px; + white-space: nowrap; + } + } +} + .prometheus-svg-container { position: relative; height: 0; diff --git a/changelogs/unreleased/38030-add-graph-value-to-hover.yml b/changelogs/unreleased/38030-add-graph-value-to-hover.yml new file mode 100644 index 0000000000000000000000000000000000000000..233db2b19c92ff9d2a4278b3dac699f7c5106dbe --- /dev/null +++ b/changelogs/unreleased/38030-add-graph-value-to-hover.yml @@ -0,0 +1,5 @@ +--- +title: Display graph values on hover within monitoring page +merge_request: 16261 +author: +type: changed diff --git a/spec/javascripts/monitoring/graph/deployment_spec.js b/spec/javascripts/monitoring/graph/deployment_spec.js index bf6ada8185ec0493101e869e915f668a73446c49..d07db871d694f0ef66a4b01bdbff0c639c13189c 100644 --- a/spec/javascripts/monitoring/graph/deployment_spec.js +++ b/spec/javascripts/monitoring/graph/deployment_spec.js @@ -11,168 +11,38 @@ const createComponent = (propsData) => { }; describe('MonitoringDeployment', () => { - const reducedDeploymentData = [deploymentData[0]]; - reducedDeploymentData[0].ref = reducedDeploymentData[0].ref.name; - reducedDeploymentData[0].xPos = 10; - reducedDeploymentData[0].time = new Date(reducedDeploymentData[0].created_at); describe('Methods', () => { - it('refText shows the ref when a tag is available', () => { - reducedDeploymentData[0].tag = '1.0'; - const component = createComponent({ - showDeployInfo: false, - deploymentData: reducedDeploymentData, - graphWidth: 440, - graphHeight: 300, - graphHeightOffset: 120, - }); - - expect( - component.refText(reducedDeploymentData[0]), - ).toEqual(reducedDeploymentData[0].ref); - }); - - it('refText shows the sha when no tag is available', () => { - reducedDeploymentData[0].tag = null; - const component = createComponent({ - showDeployInfo: false, - deploymentData: reducedDeploymentData, - graphHeight: 300, - graphWidth: 440, - graphHeightOffset: 120, - }); - - expect( - component.refText(reducedDeploymentData[0]), - ).toContain('f5bcd1'); - }); - - it('nameDeploymentClass creates a class with the prefix deploy-info-', () => { + it('should contain a hidden gradient', () => { const component = createComponent({ - showDeployInfo: false, - deploymentData: reducedDeploymentData, + showDeployInfo: true, + deploymentData, graphHeight: 300, graphWidth: 440, graphHeightOffset: 120, }); - expect( - component.nameDeploymentClass(reducedDeploymentData[0]), - ).toContain('deploy-info'); + expect(component.$el.querySelector('#shadow-gradient')).not.toBeNull(); }); it('transformDeploymentGroup translates an available deployment', () => { const component = createComponent({ showDeployInfo: false, - deploymentData: reducedDeploymentData, + deploymentData, graphHeight: 300, graphWidth: 440, graphHeightOffset: 120, }); expect( - component.transformDeploymentGroup(reducedDeploymentData[0]), + component.transformDeploymentGroup({ xPos: 16 }), ).toContain('translate(11, 20)'); }); - it('hides the deployment flag', () => { - reducedDeploymentData[0].showDeploymentFlag = false; - const component = createComponent({ - showDeployInfo: true, - deploymentData: reducedDeploymentData, - graphWidth: 440, - graphHeight: 300, - graphHeightOffset: 120, - }); - - expect(component.$el.querySelector('.js-deploy-info-box')).toBeNull(); - }); - - it('positions the flag to the left when the xPos is too far right', () => { - reducedDeploymentData[0].showDeploymentFlag = false; - reducedDeploymentData[0].xPos = 250; - const component = createComponent({ - showDeployInfo: true, - deploymentData: reducedDeploymentData, - graphWidth: 440, - graphHeight: 300, - graphHeightOffset: 120, - }); - - expect( - component.positionFlag(reducedDeploymentData[0]), - ).toBeLessThan(0); - }); - - it('shows the deployment flag', () => { - reducedDeploymentData[0].showDeploymentFlag = true; - const component = createComponent({ - showDeployInfo: true, - deploymentData: reducedDeploymentData, - graphHeight: 300, - graphWidth: 440, - graphHeightOffset: 120, - }); - - expect( - component.$el.querySelector('.js-deploy-info-box').style.display, - ).not.toEqual('display: none;'); - }); - - it('contains date, refs and the "deployed" text', () => { - reducedDeploymentData[0].showDeploymentFlag = true; - const component = createComponent({ - showDeployInfo: true, - deploymentData: reducedDeploymentData, - graphHeight: 300, - graphWidth: 440, - graphHeightOffset: 120, - }); - - expect( - component.$el.querySelectorAll('.deploy-info-text'), - ).toContainText('Deployed'); - - expect( - component.$el.querySelectorAll('.deploy-info-text'), - ).toContainText('Wed, May 31'); - - expect( - component.$el.querySelectorAll('.deploy-info-text'), - ).toContainText(component.refText(reducedDeploymentData[0])); - }); - - it('contains a link to the commit contents', () => { - reducedDeploymentData[0].showDeploymentFlag = true; - const component = createComponent({ - showDeployInfo: true, - deploymentData: reducedDeploymentData, - graphHeight: 300, - graphWidth: 440, - graphHeightOffset: 120, - }); - - expect( - component.$el.querySelectorAll('.deploy-info-text-link')[0].parentElement.getAttribute('xlink:href'), - ).not.toEqual(''); - }); - - it('should contain a hidden gradient', () => { - const component = createComponent({ - showDeployInfo: true, - deploymentData: reducedDeploymentData, - graphHeight: 300, - graphWidth: 440, - graphHeightOffset: 120, - }); - - expect(component.$el.querySelector('#shadow-gradient')).not.toBeNull(); - }); - describe('Computed props', () => { it('calculatedHeight', () => { const component = createComponent({ showDeployInfo: true, - deploymentData: reducedDeploymentData, + deploymentData, graphHeight: 300, graphWidth: 440, graphHeightOffset: 120, diff --git a/spec/javascripts/monitoring/graph/flag_spec.js b/spec/javascripts/monitoring/graph/flag_spec.js index 8ee1171419dab41a1271a5b9bb631095fbf0e5a0..2d474e9092f28c8393d463ee6423a24007553d87 100644 --- a/spec/javascripts/monitoring/graph/flag_spec.js +++ b/spec/javascripts/monitoring/graph/flag_spec.js @@ -1,5 +1,6 @@ import Vue from 'vue'; import GraphFlag from '~/monitoring/components/graph/flag.vue'; +import { deploymentData } from '../mock_data'; const createComponent = (propsData) => { const Component = Vue.extend(GraphFlag); @@ -9,11 +10,6 @@ const createComponent = (propsData) => { }).$mount(); }; -function getCoordinate(component, selector, coordinate) { - const coordinateVal = component.$el.querySelector(selector).getAttribute(coordinate); - return parseInt(coordinateVal, 10); -} - const defaultValuesComponent = { currentXCoordinate: 200, currentYCoordinate: 100, @@ -25,31 +21,111 @@ const defaultValuesComponent = { graphHeight: 300, graphHeightOffset: 120, showFlagContent: true, + realPixelRatio: 1, + timeSeries: [{ + values: [{ + time: new Date('2017-06-04T18:17:33.501Z'), + value: '1.49609375', + }], + }], + unitOfDisplay: 'ms', + currentDataIndex: 0, + legendTitle: 'Average', +}; + +const deploymentFlagData = { + ...deploymentData[0], + ref: deploymentData[0].ref.name, + xPos: 10, + time: new Date(deploymentData[0].created_at), }; describe('GraphFlag', () => { - it('has a line and a circle located at the currentXCoordinate and currentYCoordinate', () => { - const component = createComponent(defaultValuesComponent); + let component; - expect(getCoordinate(component, '.selected-metric-line', 'x1')) - .toEqual(component.currentXCoordinate); - expect(getCoordinate(component, '.selected-metric-line', 'x2')) - .toEqual(component.currentXCoordinate); + it('has a line at the currentXCoordinate', () => { + component = createComponent(defaultValuesComponent); + + expect(component.$el.style.left) + .toEqual(`${70 + component.currentXCoordinate}px`); }); - it('has a SVG with the class rect-text-metric at the currentFlagPosition', () => { - const component = createComponent(defaultValuesComponent); + describe('Deployment flag', () => { + it('shows a deployment flag when deployment data provided', () => { + const deploymentFlagComponent = createComponent({ + ...defaultValuesComponent, + deploymentFlagData, + }); + + expect( + deploymentFlagComponent.$el.querySelector('.popover-title'), + ).toContainText('Deployed'); + }); + + it('contains the ref when a tag is available', () => { + const deploymentFlagComponent = createComponent({ + ...defaultValuesComponent, + deploymentFlagData: { + ...deploymentFlagData, + sha: 'f5bcd1d9dac6fa4137e2510b9ccd134ef2e84187', + tag: true, + ref: '1.0', + }, + }); + + expect( + deploymentFlagComponent.$el.querySelector('.deploy-meta-content'), + ).toContainText('f5bcd1d9'); + + expect( + deploymentFlagComponent.$el.querySelector('.deploy-meta-content'), + ).toContainText('1.0'); + }); + + it('does not contain the ref when a tag is unavailable', () => { + const deploymentFlagComponent = createComponent({ + ...defaultValuesComponent, + deploymentFlagData: { + ...deploymentFlagData, + sha: 'f5bcd1d9dac6fa4137e2510b9ccd134ef2e84187', + tag: false, + ref: '1.0', + }, + }); + + expect( + deploymentFlagComponent.$el.querySelector('.deploy-meta-content'), + ).toContainText('f5bcd1d9'); - const svg = component.$el.querySelector('.rect-text-metric'); - expect(svg.tagName).toEqual('svg'); - expect(parseInt(svg.getAttribute('x'), 10)).toEqual(component.currentFlagPosition); + expect( + deploymentFlagComponent.$el.querySelector('.deploy-meta-content'), + ).not.toContainText('1.0'); + }); }); describe('Computed props', () => { - it('calculatedHeight', () => { - const component = createComponent(defaultValuesComponent); + beforeEach(() => { + component = createComponent(defaultValuesComponent); + }); + + it('formatTime', () => { + expect(component.formatTime).toMatch(/\d:17PM/); + }); + + it('formatDate', () => { + expect(component.formatDate).toEqual('Sun, Jun 4'); + }); + + it('cursorStyle', () => { + expect(component.cursorStyle).toEqual({ + top: '20px', + left: '270px', + height: '180px', + }); + }); - expect(component.calculatedHeight).toEqual(180); + it('flagOrientation', () => { + expect(component.flagOrientation).toEqual('left'); }); }); });