Commit ec449eeb by Johannes Edmeier

Make metrics view config bookmarkable

parent 732ee621
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import {shallow} from '@vue/test-utils'; import {shallowMount} from '@vue/test-utils';
import moment from 'moment'; import moment from 'moment';
import sbaTimeAgo from './sba-time-ago.js'; import sbaTimeAgo from './sba-time-ago.js';
...@@ -23,7 +23,7 @@ moment.now = () => +new Date(1318781879406); ...@@ -23,7 +23,7 @@ moment.now = () => +new Date(1318781879406);
describe('time-ago', () => { describe('time-ago', () => {
it('should match the snapshot', () => { it('should match the snapshot', () => {
const wrapper = shallow(sbaTimeAgo, { const wrapper = shallowMount(sbaTimeAgo, {
propsData: { propsData: {
date: moment(1318781000000) date: moment(1318781000000)
} }
......
...@@ -39,7 +39,7 @@ ...@@ -39,7 +39,7 @@
</ul> </ul>
</nav> </nav>
</div> </div>
<div class="column is-clipped" v-if="selectedDomain"> <div class="column" v-if="selectedDomain">
<h1 class="heading">MBeans</h1> <h1 class="heading">MBeans</h1>
<div class="m-bean card" :class="{'is-active': mBean === selectedMBean}" <div class="m-bean card" :class="{'is-active': mBean === selectedMBean}"
v-for="mBean in selectedDomain.mBeans" :key="mBean.descriptor.raw" :id="mBean.descriptor.raw" v-for="mBean in selectedDomain.mBeans" :key="mBean.descriptor.raw" :id="mBean.descriptor.raw"
...@@ -162,8 +162,10 @@ ...@@ -162,8 +162,10 @@
}, },
watch: { watch: {
'$route.query': { '$route.query': {
handler: 'updateSelection', immediate: true,
immediate: true handler() {
this.selected = this.$route.query;
}
}, },
selected() { selected() {
if (!_.isEqual(this.selected, !this.$route.query)) { if (!_.isEqual(this.selected, !this.$route.query)) {
...@@ -209,9 +211,6 @@ ...@@ -209,9 +211,6 @@
mBean: mBean && mBean.descriptor.raw, mBean: mBean && mBean.descriptor.raw,
view: view || (mBean ? (mBean.attr ? 'attributes' : (mBean.op ? 'operations' : null)) : null) view: view || (mBean ? (mBean.attr ? 'attributes' : (mBean.op ? 'operations' : null)) : null)
}; };
},
updateSelection() {
this.selected = this.$route.query;
} }
} }
} }
......
...@@ -72,8 +72,10 @@ ...@@ -72,8 +72,10 @@
:key="metric.name" :key="metric.name"
:metric-name="metric.name" :metric-name="metric.name"
:tag-selections="metric.tagSelections" :tag-selections="metric.tagSelections"
:statistic-types="metric.types"
:instance="instance" :instance="instance"
@remove="removeMetric" @remove="removeMetric"
@type-select="handleTypeSelect"
/> />
</div> </div>
</section> </section>
...@@ -84,6 +86,21 @@ ...@@ -84,6 +86,21 @@
import _ from 'lodash'; import _ from 'lodash';
import Metric from './metric'; import Metric from './metric';
const stringify = metrics => {
return {q: metrics.map(JSON.stringify)};
};
const parse = query => {
if (!query.q) {
return [];
}
if (query.q instanceof Array) {
return query.q.map(JSON.parse);
} else {
return JSON.parse(query.q);
}
};
export default { export default {
components: {Metric}, components: {Metric},
props: { props: {
...@@ -105,12 +122,33 @@ ...@@ -105,12 +122,33 @@
this.fetchMetricIndex(); this.fetchMetricIndex();
}, },
watch: { watch: {
selectedMetric: 'fetchAvailableTags' selectedMetric: 'fetchAvailableTags',
metrics: {
deep: true,
handler() {
this.$router.replace({
name: 'instance/metrics',
query: stringify(this.metrics)
})
}
},
'$route.query': {
immediate: true,
handler() {
this.metrics = parse(this.$route.query);
}
}
}, },
methods: { methods: {
handleSubmit() { handleSubmit() {
this.addMetric(this.selectedMetric, this.selectedTags) this.addMetric(this.selectedMetric, this.selectedTags)
}, },
handleTypeSelect(metricName, statistic, type) {
const metric = this.metrics.find(m => m.name === metricName);
if (metric) {
metric.types = {...metric.types, [statistic]: type}
}
},
removeMetric(metricName, idxTagSelection) { removeMetric(metricName, idxTagSelection) {
const idxMetric = this.metrics.findIndex(m => m.name === metricName); const idxMetric = this.metrics.findIndex(m => m.name === metricName);
if (idxMetric >= 0) { if (idxMetric >= 0) {
...@@ -131,7 +169,8 @@ ...@@ -131,7 +169,8 @@
} else { } else {
this.metrics = _.sortBy([...this.metrics, { this.metrics = _.sortBy([...this.metrics, {
name: metricName, name: metricName,
tagSelections: [{...tagSelection}] tagSelections: [{...tagSelection}],
types: {}
}], [m => m.name]); }], [m => m.name]);
} }
} }
......
...@@ -15,16 +15,16 @@ ...@@ -15,16 +15,16 @@
--> -->
<template> <template>
<table class="metrics table is-fullwidth is-bordered is-narrow"> <table class="metrics table is-fullwidth">
<thead> <thead>
<tr> <tr>
<th v-text="metricName"/> <th class="metrics__label" v-text="metricName"/>
<th class="metrics__statistic-name" <th class="metrics__statistic-name"
v-for="statistic in statistics" v-for="statistic in statistics"
:key="`head-${statistic.name}`"> :key="`head-${statistic}`">
<span v-text="statistic.name"/> <span v-text="statistic"/>
<div class="select is-small is-pulled-right"> <div class="select is-small is-pulled-right">
<select v-model="statistic.type"> <select :value="statisticTypes[statistic]" @input="$emit('type-select', metricName, statistic, $event.target.value)">
<option :value="undefined">-</option> <option :value="undefined">-</option>
<option value="integer">Integer</option> <option value="integer">Integer</option>
<option value="float">Float</option> <option value="float">Float</option>
...@@ -39,7 +39,7 @@ ...@@ -39,7 +39,7 @@
</thead> </thead>
<tbody> <tbody>
<tr v-for="(tags, idx) in tagSelections" :key="idx"> <tr v-for="(tags, idx) in tagSelections" :key="idx">
<td> <td class="metrics__label">
<span v-text="getLabel(tags)"/> <span v-text="getLabel(tags)"/>
<span class="has-text-warning" v-if="errors[idx]" :title="errors[idx]"> <span class="has-text-warning" v-if="errors[idx]" :title="errors[idx]">
<font-awesome-icon icon="exclamation-triangle"/> <font-awesome-icon icon="exclamation-triangle"/>
...@@ -47,10 +47,10 @@ ...@@ -47,10 +47,10 @@
</td> </td>
<td class="metrics__statistic-value" <td class="metrics__statistic-value"
v-for="statistic in statistics" v-for="statistic in statistics"
:key="`value-${idx}-${statistic.name}`" :key="`value-${idx}-${statistic}`"
v-text="getValue(measurements[idx], statistic)" v-text="getValue(measurements[idx], statistic)"
/> />
<td> <td class="metrics__actions">
<sba-icon-button :icon="'trash'" @click.stop="handleRemove(idx)"/> <sba-icon-button :icon="'trash'" @click.stop="handleRemove(idx)"/>
</td> </td>
</tr> </tr>
...@@ -90,6 +90,10 @@ ...@@ -90,6 +90,10 @@
tagSelections: { tagSelections: {
type: Array, type: Array,
default: () => [{}] default: () => [{}]
},
statisticTypes: {
type: Object,
default: () => ({})
} }
}, },
data: () => ({ data: () => ({
...@@ -102,11 +106,11 @@ ...@@ -102,11 +106,11 @@
this.$emit('remove', this.metricName, idx); this.$emit('remove', this.metricName, idx);
}, },
getValue(measurements, statistic) { getValue(measurements, statistic) {
const measurement = measurements && measurements.find(m => m.statistic === statistic.name); const measurement = measurements && measurements.find(m => m.statistic === statistic);
if (!measurement) { if (!measurement) {
return undefined; return undefined;
} }
const type = statistic && statistic.type; const type = this.statisticTypes[statistic]
switch (type) { switch (type) {
case 'integer': case 'integer':
return measurement.value.toFixed(0); return measurement.value.toFixed(0);
...@@ -124,8 +128,8 @@ ...@@ -124,8 +128,8 @@
}, },
getLabel(tags) { getLabel(tags) {
return _.entries(tags).filter(([, value]) => typeof value !== 'undefined') return _.entries(tags).filter(([, value]) => typeof value !== 'undefined')
.map(pair => pair.join('=')) .map(pair => pair.join(':'))
.join(' '); .join('\n') || '(no tags)';
}, },
async fetchMetric(tags, idx) { async fetchMetric(tags, idx) {
try { try {
...@@ -133,14 +137,11 @@ ...@@ -133,14 +137,11 @@
this.$set(this.errors, idx, null); this.$set(this.errors, idx, null);
this.$set(this.measurements, idx, response.data.measurements); this.$set(this.measurements, idx, response.data.measurements);
if (idx === 0) { if (idx === 0) {
response.data.measurements.map(m => m.statistic) this.statistics = response.data.measurements.map(m => m.statistic);
.filter(s => !this.statistics.some(stat => stat.name === s))
.map(s => ({name: s, type: undefined}))
.forEach(s => this.statistics.push(s));
} }
} catch (error) { } catch (error) {
console.warn(`Fetching metric ${this.metricName} failed:`, error); console.warn(`Fetching metric ${this.metricName} failed:`, error);
this.errors[idx] = error; this.$set(this.errors, idx, error);
} }
}, },
fetchAllTags() { fetchAllTags() {
...@@ -166,17 +167,23 @@ ...@@ -166,17 +167,23 @@
} }
</script> </script>
<style lang="scss"> <style lang="scss">
table.metrics {
table-layout: fixed;
}
.metrics { table .metrics {
&__label {
width: 300px;
white-space: pre-wrap;
}
&__actions {
width: 1px;
vertical-align: middle;
}
&__statistic-name * { &__statistic-name * {
vertical-align: middle; vertical-align: middle;
} }
&__statistic-value { &__statistic-value {
text-align: right; text-align: right;
vertical-align: middle;
} }
} }
</style> </style>
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