-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Core usage metrics v1 (merge to side-branch) (#8238) * restructure menu layout per designs * setup new routing that will set the stage for a metrics landing page * fix formatting * Revert "fix formatting" This reverts commit e77cdec. * fix formatting * small styling changes * change request routing to metrics * rename route js file * Core usage metrics v2 (#8263) * restructure menu layout per designs * setup new routing that will set the stage for a metrics landing page * fix formatting * Revert "fix formatting" This reverts commit e77cdec. * fix formatting * small styling changes * change request routing to metrics * rename route js file * setup selectable card component and api request * add token and http request models to route and template * add entities to route and template * clean up * add breadcrumbs and some clean up work * remove unused selectable-card component * refactor to a serializer * move adapters, serializers, and models into metrics folder * remove unused file * address pr comments * address pr comments * Core Usage Metrics V3 (#8316) * restructure menu layout per designs * setup new routing that will set the stage for a metrics landing page * fix formatting * Revert "fix formatting" This reverts commit e77cdec. * fix formatting * small styling changes * change request routing to metrics * rename route js file * setup selectable card component and api request * add token and http request models to route and template * add entities to route and template * clean up * add breadcrumbs and some clean up work * remove unused selectable-card component * setup smaller http request bar chart * refactor to a serializer * move adapters, serializers, and models into metrics folder * remove unused file * setup change part of component * fix broken model * add conditional class * setting up computed properties in new component * small fixes * setup components * minor fixes * rename * clean up * firefox fix * remove shadow bars * move out of metrics folders * modify permissions to show difference between token entities and requests * make tests * fix class names and associated tests * clean up * fix text overflow in non-chrome browsers * address pr comments, specifically class names and tests * move into one component * clean up component descriptions in comments * small wording changes * fix for accessibility * address pr comments around component examples for storybook * fix test * fix failing test * fix test
- Loading branch information
1 parent
0937a58
commit 2a52c1a
Showing
31 changed files
with
803 additions
and
64 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import Application from '../application'; | ||
|
||
export default Application.extend({ | ||
queryRecord() { | ||
return this.ajax(this.urlForQuery(), 'GET').then(resp => { | ||
resp.id = resp.request_id; | ||
return resp; | ||
}); | ||
}, | ||
|
||
urlForQuery() { | ||
return this.buildURL() + '/internal/counters/entities'; | ||
}, | ||
}); |
2 changes: 1 addition & 1 deletion
2
ui/app/adapters/requests.js → ui/app/adapters/metrics/http-requests.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import Application from '../application'; | ||
|
||
export default Application.extend({ | ||
queryRecord() { | ||
return this.ajax(this.urlForQuery(), 'GET').then(resp => { | ||
resp.id = resp.request_id; | ||
return resp; | ||
}); | ||
}, | ||
|
||
urlForQuery() { | ||
return this.buildURL() + '/internal/counters/tokens'; | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
import Component from '@ember/component'; | ||
import d3 from 'd3-selection'; | ||
import d3Scale from 'd3-scale'; | ||
import d3Axis from 'd3-axis'; | ||
import d3TimeFormat from 'd3-time-format'; | ||
import { assign } from '@ember/polyfills'; | ||
import { computed } from '@ember/object'; | ||
import { run } from '@ember/runloop'; | ||
import { task, waitForEvent } from 'ember-concurrency'; | ||
|
||
/** | ||
* @module HttpRequestsBarChartSmall | ||
* The HttpRequestsBarChartSmall is a simplified version of the HttpRequestsBarChart component. | ||
* | ||
* | ||
* @example | ||
* ```js | ||
* <HttpRequestsBarChartSmall @counters={counters}/> | ||
* ``` | ||
* | ||
* @param counters=null {Array} - A list of objects containing the total number of HTTP Requests for each month. `counters` should be the response from the `/internal/counters/requests`. | ||
* The response is then filtered showing only the 12 most recent months of data. This property is called filteredHttpsRequests, like: | ||
* const FILTERED_HTTPS_REQUESTS = [ | ||
* { start_time: '2018-11-01T00:00:00Z', total: 5500 }, | ||
* { start_time: '2018-12-01T00:00:00Z', total: 4500 }, | ||
* { start_time: '2019-01-01T00:00:00Z', total: 5000 }, | ||
* { start_time: '2019-02-01T00:00:00Z', total: 5000 }, | ||
* ]; | ||
*/ | ||
|
||
const HEIGHT = 125; | ||
const UI_GRAY_300 = '#bac1cc'; | ||
const UI_GRAY_100 = '#ebeef2'; | ||
|
||
export default Component.extend({ | ||
classNames: ['http-requests-bar-chart-small'], | ||
counters: null, | ||
margin: Object.freeze({ top: 24, right: 16, bottom: 24, left: 16 }), | ||
padding: 0.04, | ||
width: 0, | ||
height() { | ||
const { margin } = this; | ||
return HEIGHT - margin.top - margin.bottom; | ||
}, | ||
parsedCounters: computed('counters', function() { | ||
// parse the start times so bars display properly | ||
const { counters } = this; | ||
counters.reverse(); | ||
return counters.map((counter, index) => { | ||
return assign({}, counter, { | ||
start_time: d3TimeFormat.isoParse(counter.start_time), | ||
fill_color: index === counters.length - 1 ? UI_GRAY_300 : UI_GRAY_100, | ||
}); | ||
}); | ||
}), | ||
|
||
yScale: computed('parsedCounters', 'height', function() { | ||
const { parsedCounters } = this; | ||
const height = this.height(); | ||
const counterTotals = parsedCounters.map(c => c.total); | ||
|
||
return d3Scale | ||
.scaleLinear() | ||
.domain([0, Math.max(...counterTotals)]) | ||
.range([height, 0]); | ||
}), | ||
|
||
xScale: computed('parsedCounters', 'width', function() { | ||
const { parsedCounters, width, margin, padding } = this; | ||
return d3Scale | ||
.scaleBand() | ||
.domain(parsedCounters.map(c => c.start_time)) | ||
.rangeRound([0, width - margin.left - margin.right], 0.05) | ||
.paddingInner(padding) | ||
.paddingOuter(padding); | ||
}), | ||
|
||
didInsertElement() { | ||
this._super(...arguments); | ||
const { margin } = this; | ||
|
||
// set the width after the element has been rendered because the chart axes depend on it. | ||
// this helps us avoid an arbitrary hardcoded width which causes alignment & resizing problems. | ||
run.schedule('afterRender', this, () => { | ||
this.set('width', this.element.clientWidth - margin.left - margin.right); | ||
this.renderBarChart(); | ||
}); | ||
}, | ||
|
||
didUpdateAttrs() { | ||
this.renderBarChart(); | ||
}, | ||
|
||
renderBarChart() { | ||
const { margin, width, xScale, yScale, parsedCounters, elementId } = this; | ||
const height = this.height(); | ||
const barChartSVG = d3.select('.http-requests-bar-chart-small'); | ||
const barsContainer = d3.select(`#bars-container-${elementId}`); | ||
|
||
d3.select('.http-requests-bar-chart') | ||
.attr('width', width + margin.left + margin.right) | ||
.attr('height', height + margin.top + margin.bottom) | ||
.attr('viewBox', `0 0 ${width} ${height}`); | ||
|
||
const xAxis = d3Axis | ||
.axisBottom(xScale) | ||
.tickFormat('') | ||
.tickValues([]) | ||
.tickSizeOuter(0); | ||
|
||
barChartSVG | ||
.select('g.x-axis') | ||
.attr('transform', `translate(0,${height})`) | ||
.call(xAxis); | ||
|
||
const bars = barsContainer.selectAll('.bar').data(parsedCounters, c => +c.start_time); | ||
|
||
const barsEnter = bars | ||
.enter() | ||
.append('rect') | ||
.attr('class', 'bar'); | ||
|
||
bars | ||
.merge(barsEnter) | ||
.attr('x', counter => xScale(counter.start_time)) | ||
.attr('y', () => yScale(0)) | ||
.attr('width', xScale.bandwidth()) | ||
.attr('height', counter => height - yScale(counter.total) - 5) // subtract 5 to provide the gap between the xAxis and the bars | ||
.attr('y', counter => yScale(counter.total)) | ||
.attr('fill', counter => counter.fill_color) | ||
.attr('stroke', counter => counter.fill_color); | ||
|
||
bars.exit().remove(); | ||
}, | ||
|
||
updateDimensions() { | ||
const newWidth = this.element.clientWidth; | ||
const { margin } = this; | ||
|
||
this.set('width', newWidth - margin.left - margin.right); | ||
this.renderBarChart(); | ||
}, | ||
|
||
waitForResize: task(function*() { | ||
while (true) { | ||
yield waitForEvent(window, 'resize'); | ||
run.scheduleOnce('afterRender', this, 'updateDimensions'); | ||
} | ||
}) | ||
.on('didInsertElement') | ||
.cancelOn('willDestroyElement') | ||
.drop(), | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import Component from '@ember/component'; | ||
import { computed } from '@ember/object'; | ||
|
||
/** | ||
* @module SelectableCardContainer | ||
* SelectableCardContainer components are used to hold SelectableCard components. They act as a CSS grid container, and change grid configurations based on the boolean of @gridContainer. | ||
* | ||
* @example | ||
* ```js | ||
* <SelectableCardContainer @counters={{model}} @gridContainer="true" /> | ||
* ``` | ||
* @param counters=null {Object} - Counters is an object that returns total entities, tokens, and an array of objects with the total https request per month. | ||
* @param gridContainer=false {Boolean} - gridContainer is optional. If true, it's telling the container it will have a nested CSS grid. | ||
* | ||
* const MODEL = { | ||
* totalEntities: 0, | ||
* httpsRequests: [{ start_time: '2019-04-01T00:00:00Z', total: 5500 }], | ||
* totalTokens: 1, | ||
* }; | ||
*/ | ||
|
||
export default Component.extend({ | ||
classNameBindings: ['isGridContainer'], | ||
counters: null, | ||
gridContainer: false, | ||
isGridContainer: computed('counters', function() { | ||
return this.counters.httpsRequests.length > 1 | ||
? 'selectable-card-container has-grid' | ||
: 'selectable-card-container'; | ||
}), | ||
totalHttpRequests: computed('counters', function() { | ||
let httpsRequestsArray = this.counters.httpsRequests || []; | ||
return httpsRequestsArray.firstObject.total; | ||
}), | ||
// Limit number of months returned to the most recent 12 | ||
filteredHttpsRequests: computed('counters', function() { | ||
let httpsRequestsArray = this.counters.httpsRequests || []; | ||
if (httpsRequestsArray.length > 12) { | ||
httpsRequestsArray = httpsRequestsArray.slice(0, 12); | ||
} | ||
return httpsRequestsArray; | ||
}), | ||
percentChange: computed('counters', function() { | ||
let httpsRequestsArray = this.counters.httpsRequests || []; | ||
let lastTwoMonthsArray = httpsRequestsArray.slice(0, 2); | ||
let previousMonthVal = lastTwoMonthsArray.lastObject.total; | ||
let thisMonthVal = lastTwoMonthsArray.firstObject.total; | ||
|
||
let percentChange = (((previousMonthVal - thisMonthVal) / previousMonthVal) * 100).toFixed(1); | ||
// a negative value indicates a percentage increase, so we swap the value | ||
percentChange = -percentChange; | ||
return percentChange; | ||
}), | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import Component from '@ember/component'; | ||
import { computed } from '@ember/object'; | ||
/** | ||
* @module SelectableCard | ||
* SelectableCard components are card-like components that display a title, total, subtotal, and anything after they yield. | ||
* They are designed to be used in containers that act as flexbox or css grid containers. | ||
* | ||
* @example | ||
* ```js | ||
* <SelectableCard @cardTitle="Tokens" @total={{totalHttpRequests}} @subText="Total" @gridContainer={{gridContainer}}/> | ||
* ``` | ||
* @param cardTitle='' {String} - cardTitle displays the card title | ||
* @param total=0 {Number} - the Total number displays like a title, it's the largest text in the component | ||
* @param subText='' {String} - subText describes the total | ||
* @param gridContainer=false {Boolean} - Optional parameter used to display CSS grid item class. | ||
*/ | ||
|
||
export default Component.extend({ | ||
cardTitle: '', | ||
total: 0, | ||
subText: '', | ||
gridContainer: false, | ||
tagName: '', // do not wrap component with div | ||
formattedCardTitle: computed('total', function() { | ||
const { cardTitle, total } = this; | ||
|
||
if (cardTitle === 'Tokens') { | ||
return total !== 1 ? 'Tokens' : 'Token'; | ||
} else if (cardTitle === 'Entities') { | ||
return total !== 1 ? 'Entities' : 'Entity'; | ||
} | ||
|
||
return cardTitle; | ||
}), | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import DS from 'ember-data'; | ||
const { attr } = DS; | ||
|
||
/* sample response | ||
{ | ||
"request_id": "75cbaa46-e741-3eba-2be2-325b1ba8f03f", | ||
"lease_id": "", | ||
"renewable": false, | ||
"lease_duration": 0, | ||
"data": { | ||
"counters": { | ||
"entities": { | ||
"total": 1 | ||
} | ||
} | ||
}, | ||
"wrap_info": null, | ||
"warnings": null, | ||
"auth": null | ||
} | ||
*/ | ||
|
||
export default DS.Model.extend({ | ||
entities: attr('object'), | ||
}); |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import DS from 'ember-data'; | ||
const { attr } = DS; | ||
|
||
/* sample response | ||
{ | ||
"request_id": "75cbaa46-e741-3eba-2be2-325b1ba8f03f", | ||
"lease_id": "", | ||
"renewable": false, | ||
"lease_duration": 0, | ||
"data": { | ||
"counters": { | ||
"service_tokens": { | ||
"total": 1 | ||
} | ||
} | ||
}, | ||
"wrap_info": null, | ||
"warnings": null, | ||
"auth": null | ||
} | ||
*/ | ||
|
||
export default DS.Model.extend({ | ||
service_tokens: attr('object'), | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import Route from '@ember/routing/route'; | ||
import ClusterRoute from 'vault/mixins/cluster-route'; | ||
|
||
export default Route.extend(ClusterRoute, { | ||
model() { | ||
return {}; | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import ClusterRouteBase from '../cluster-route-base'; | ||
|
||
export default ClusterRouteBase.extend({ | ||
model() { | ||
return this.store.queryRecord('metrics/http-requests', {}); | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import Route from '@ember/routing/route'; | ||
import ClusterRoute from 'vault/mixins/cluster-route'; | ||
import { hash } from 'rsvp'; | ||
|
||
export default Route.extend(ClusterRoute, { | ||
model() { | ||
let totalEntities = this.store.queryRecord('metrics/entity', {}).then(response => { | ||
return response.entities.total; | ||
}); | ||
|
||
let httpsRequests = this.store.queryRecord('metrics/http-requests', {}).then(response => { | ||
let reverseArray = response.counters.reverse(); | ||
return reverseArray; | ||
}); | ||
|
||
let totalTokens = this.store.queryRecord('metrics/token', {}).then(response => { | ||
return response.service_tokens.total; | ||
}); | ||
|
||
return hash({ | ||
totalEntities, | ||
httpsRequests, | ||
totalTokens, | ||
}); | ||
}, | ||
}); |
Oops, something went wrong.