3
0

Improve progress bar page block and number display

This commit is contained in:
Jože Fortun
2022-11-15 17:07:40 +01:00
parent 45b0c00580
commit 8407ba2e14
12 changed files with 644 additions and 314 deletions

View File

@@ -1,15 +1,42 @@
<template>
<div>
<b-form-group
:label="$t('kind.number.displayType.label')"
>
<b-form-radio-group
v-model="f.options.display"
button-variant="outline-primary"
:options="displayOptions"
buttons
/>
</b-form-group>
<b-row>
<b-col
cols="12"
sm="6"
>
<b-form-group
:label="$t('kind.number.displayType.label')"
>
<b-form-radio-group
v-model="f.options.display"
button-variant="outline-primary"
:options="displayOptions"
buttons
/>
</b-form-group>
</b-col>
<b-col
cols="12"
sm="6"
>
<label class="d-block mb-3">
{{ $t('kind.number.precisionLabel') }} ({{ f.options.precision }})
</label>
<b-form-input
v-model="f.options.precision"
:placeholder="$t('kind.number.precisionPlaceholder')"
type="range"
min="0"
max="6"
size="lg"
class="mt-1 mb-2"
/>
</b-col>
</b-row>
<hr>
<b-row>
<template v-if="f.options.display === 'number'">
@@ -42,24 +69,6 @@
</b-col>
</template>
<b-col
cols="12"
sm="6"
>
<label class="d-block mb-3">
{{ $t('kind.number.precisionLabel') }} ({{ f.options.precision }})
</label>
<b-form-input
v-model="f.options.precision"
:placeholder="$t('kind.number.precisionPlaceholder')"
type="range"
min="0"
max="6"
size="lg"
class="mt-1 mb-2"
/>
</b-col>
<template v-if="f.options.display === 'number'">
<b-col
cols="12"
@@ -74,6 +83,49 @@
/>
</b-form-group>
</b-col>
<b-col
cols="12"
>
<b-form-group
v-if="f.options.display === 'number'"
:label="$t('kind.number.examplesLabel')"
>
<table
style="width: 100%;"
>
<tr>
<th>{{ $t('kind.number.exampleInput') }}</th>
<th>{{ $t('kind.number.exampleFormat') }}</th>
<th>{{ $t('kind.number.exampleResult') }}</th>
</tr>
<tr>
<td>10000.234</td>
<td>$0.00</td>
<td>$10000.23</td>
</tr>
<tr>
<td>0.974878234</td>
<td>0.000%</td>
<td>97.488%</td>
</tr>
<tr>
<td>1</td>
<td>0%</td>
<td>100%</td>
</tr>
<tr>
<td>238</td>
<td>00:00:00</td>
<td>0:03:58</td>
</tr>
</table>
</b-form-group>
</b-col>
</template>
<template v-if="f.options.display === 'progress'">
@@ -82,11 +134,27 @@
sm="6"
>
<b-form-group
:label="$t('kind.number.maximumValue')"
:label="$t('kind.number.progress.minimumValue')"
>
<b-form-input
v-model="f.options.min"
type="number"
number
/>
</b-form-group>
</b-col>
<b-col
cols="12"
sm="6"
>
<b-form-group
:label="$t('kind.number.progress.maximumValue')"
>
<b-form-input
v-model="f.options.max"
type="number"
number
/>
</b-form-group>
</b-col>
@@ -118,6 +186,7 @@
>
{{ $t('kind.number.progress.show.value') }}
</b-form-checkbox>
<b-form-checkbox
v-model="f.options.animated"
>
@@ -140,6 +209,7 @@
>
{{ $t('kind.number.progress.show.relative') }}
</b-form-checkbox>
<b-form-checkbox
v-model="f.options.showProgress"
>
@@ -212,6 +282,8 @@
</template>
</b-row>
<hr>
<b-form-group
:label=" $t('kind.number.liveExample')"
class="mb-0 w-100"
@@ -226,7 +298,7 @@
<b-form-input
v-model="liveExample"
type="number"
step="0.1"
number
/>
</b-col>
@@ -241,42 +313,6 @@
</b-col>
</b-row>
</b-form-group>
<b-form-group
v-if="f.options.display === 'number'"
:label="$t('kind.number.examplesLabel')"
class="mt-3"
>
<table
style="width: 100%;"
>
<tr>
<th>{{ $t('kind.number.exampleInput') }}</th>
<th>{{ $t('kind.number.exampleFormat') }}</th>
<th>{{ $t('kind.number.exampleResult') }}</th>
</tr>
<tr>
<td>10000.234</td>
<td>$0.00</td>
<td>$10000.23</td>
</tr>
<tr>
<td>0.974878234</td>
<td>0.000%</td>
<td>97.488%</td>
</tr>
<tr>
<td>1</td>
<td>0%</td>
<td>100%</td>
</tr>
<tr>
<td>238</td>
<td>00:00:00</td>
<td>0:03:58</td>
</tr>
</table>
</b-form-group>
</div>
</template>
@@ -360,7 +396,7 @@ export default {
this.mock.field.apply({ name: 'mockField' })
this.mock.module = new compose.Module({ fields: [this.mock.field] }, this.namespace)
this.mock.record = new compose.Record(this.mock.module, { })
this.liveExample = this.field.options.display === 'number' ? 123.45679 : 33
this.liveExample = this.field.options.display === 'number' ? 123.45679 : 33.45679
},
methods: {

View File

@@ -42,6 +42,7 @@
v-model="value[ctx.index]"
autocomplete="off"
type="number"
number
class="mr-2"
/>
</b-input-group>
@@ -56,6 +57,7 @@
v-model="value"
autocomplete="off"
type="number"
number
/>
</b-input-group>
<errors :errors="errors" />

View File

@@ -5,23 +5,22 @@
<div v-if="field.options.display === 'number'" :class="classes">{{ formatted }}</div>
<div v-else>
<b-progress
<c-progress
v-for="(v, i) in formatted"
:key="i"
:max="field.options.max"
height="1.5rem"
class="bg-light"
:value="parseFloat(v)"
:min="parseFloat(field.options.min)"
:max="parseFloat(field.options.max)"
:labeled="field.options.showValue"
:relative="field.options.showRelative"
:progress="field.options.showProgress"
:striped="field.options.striped"
:animated="field.options.animated"
:variant="field.options.variant"
:thresholds="field.options.thresholds"
:class="{ 'mt-2': i }"
>
<b-progress-bar
:value="v"
:striped="field.options.striped"
:animated="field.options.animated"
:variant="getProgressVariant(v)"
>
{{ getProgressLabel(v) }}
</b-progress-bar>
</b-progress>
style="height: 1.5rem;"
/>
</div>
<errors :errors="errors" />
@@ -30,14 +29,20 @@
<script>
import base from './base'
import { components } from '@cortezaproject/corteza-vue'
const { CProgress } = components
export default {
components: {
CProgress,
},
extends: base,
computed: {
formatted () {
if (!this.value) {
return
if (this.value === undefined) {
return this.field.options.display === 'number' ? undefined : [this.field.options.min]
}
const value = this.field.isMulti ? this.value : [this.value]
@@ -45,45 +50,9 @@ export default {
if (this.field.options.display === 'number') {
return value.map(v => this.field.formatValue(v)).join(this.field.options.multiDelimiter)
} else {
return value
return value.length ? value : [this.field.options.min]
}
},
sortedVariants () {
return [...this.field.options.thresholds].filter(t => t.value >= 0).sort((a, b) => b.value - a.value)
},
},
methods: {
getProgressLabel (value) {
const { max, showValue, showRelative, showProgress } = this.field.options
if (!showValue) {
return
}
if (showRelative) {
// https://stackoverflow.com/a/21907972/17926309
value = `${Math.round(((value / max) * 100) * 100) / 100}%`
}
if (showProgress) {
value = `${value} / ${showRelative ? '100' : max}${showRelative ? '%' : ''}`
}
return value
},
getProgressVariant (value) {
let progressVariant = this.field.options.variant
if (this.field.options.thresholds.length) {
const { variant } = this.sortedVariants.find(t => value >= t.value) || {}
progressVariant = variant || progressVariant
}
return progressVariant
},
},
}
</script>

View File

@@ -13,22 +13,22 @@
<div
v-else
class="h-100"
class="d-flex h-100"
:class="{ 'p-2': block.style.wrap.kind === 'card' }"
>
<b-progress
<c-progress
:value="value"
:min="min"
:max="max"
class="h-100 bg-light"
>
<b-progress-bar
:value="value"
:striped="options.display.striped"
:animated="options.display.animated"
:variant="progressVariant"
>
{{ progressLabel }}
</b-progress-bar>
</b-progress>
:labeled="options.display.showValue"
:relative="options.display.showRelative"
:progress="options.display.showProgress"
:striped="options.display.striped"
:animated="options.display.animated"
:variant="options.display.variant"
:thresholds="options.display.thresholds"
class="flex-fill h-100"
/>
</div>
</wrap>
</template>
@@ -36,65 +36,26 @@
<script>
import base from './base'
import { NoID } from '@cortezaproject/corteza-js'
import { components } from '@cortezaproject/corteza-vue'
import { evaluatePrefilter } from 'corteza-webapp-compose/src/lib/record-filter'
const { CProgress } = components
export default {
extends: base,
props: {
components: {
CProgress,
},
extends: base,
data () {
return {
processing: false,
value: undefined,
min: undefined,
max: undefined,
}
},
computed: {
progress () {
const { value = 0, max = 100 } = this
return 100 * (value / max)
},
progressLabel () {
let { value } = this
const { showValue, showRelative, showProgress } = this.options.display || {}
if (!showValue) {
return
}
if (showRelative) {
// https://stackoverflow.com/a/21907972/17926309
value = `${Math.round(((value / this.max) * 100) * 100) / 100}%`
}
if (showProgress) {
value = `${value} / ${showRelative ? '100' : this.max}${showRelative ? '%' : ''}`
}
return value
},
sortedVariants () {
return [...this.options.display.thresholds].filter(t => t.value >= 0).sort((a, b) => b.value - a.value)
},
progressVariant () {
const { variant } = this.options.display || {}
let progressVariant = variant
if (this.options.display.thresholds.length) {
const { variant } = this.sortedVariants.find(t => this.progress >= t.value) || {}
progressVariant = variant || progressVariant
}
return progressVariant
},
},
watch: {
'record.recordID': {
immediate: true,
@@ -102,6 +63,13 @@ export default {
this.refresh()
},
},
options: {
deep: true,
handler () {
this.update()
},
},
},
created () {
@@ -134,6 +102,14 @@ export default {
userID: (this.$auth.user || {}).userID || NoID,
}),
},
minValue: {
filter: evaluatePrefilter(this.options.minValue.filter, {
record: this.record,
recordID: (this.record || {}).recordID || NoID,
ownerID: (this.record || {}).ownedBy || NoID,
userID: (this.$auth.user || {}).userID || NoID,
}),
},
maxValue: {
filter: evaluatePrefilter(this.options.maxValue.filter, {
record: this.record,
@@ -144,10 +120,12 @@ export default {
},
}
return this.block.fetch(additionalOptions, this.$ComposeAPI, namespaceID).then(({ value, max }) => {
this.value = value
this.max = max
}).catch(this.toastErrorHandler(this.$t('progress.fetch-failed')))
return this.block.fetch(additionalOptions, this.$ComposeAPI, namespaceID)
.then(({ value, min = 0, max = 100 }) => {
this.min = min
this.max = max
this.value = value
}).catch(this.toastErrorHandler(this.$t('progress.fetch-failed')))
.finally(() => {
this.processing = false
})

View File

@@ -9,6 +9,22 @@
</h5>
<b-row>
<b-col
v-if="!options.value.moduleID"
cols="12"
>
<b-form-group
:label="$t('progress.value.default.label')"
:description="$t('progress.value.default.description')"
>
<b-form-input
v-model="options.value.default"
type="number"
number
/>
</b-form-group>
</b-col>
<b-col
cols="12"
>
@@ -91,12 +107,132 @@
<hr>
<template>
<h5 class="text-primary">
{{ $t('progress.value.min') }}
</h5>
<b-row>
<b-col
v-if="!options.minValue.moduleID"
cols="12"
>
<b-form-group
:label="$t('progress.value.default.label')"
:description="$t('progress.value.default.description')"
>
<b-form-input
v-model="options.minValue.default"
type="number"
number
/>
</b-form-group>
</b-col>
<b-col
cols="12"
>
<b-form-group
:label="$t('progress.module.label')"
>
<vue-select
v-model="options.minValue.moduleID"
label="name"
:placeholder="$t('progress.module.select')"
:options="modules"
:reduce="m => m.moduleID"
class="bg-white"
/>
</b-form-group>
</b-col>
<template v-if="options.minValue.moduleID">
<b-col
cols="12"
>
<b-form-group :label="$t('metric.edit.filterLabel')">
<b-form-textarea
v-model="options.minValue.filter"
placeholder="(A > B) OR (A < C)"
class="mb-1"
/>
<b-form-text>
<i18next
path="metric.edit.filterFootnote"
tag="label"
>
<code>${recordID}</code>
<code>${ownerID}</code>
<code>${userID}</code>
</i18next>
</b-form-text>
</b-form-group>
</b-col>
<b-col
cols="12"
sm="6"
>
<b-form-group
:label="$t('progress.field.label')"
>
<vue-select
v-model="options.minValue.field"
:placeholder="$t('progress.field.select')"
:options="minValueModuleFields"
:reduce="f => f.name"
class="bg-white"
@input="fieldChanged($event, options.minValue)"
/>
</b-form-group>
</b-col>
<b-col
cols="12"
sm="6"
>
<b-form-group
:label="$t('progress.aggregate.label')"
>
<vue-select
v-model="options.minValue.operation"
label="name"
:disabled="!options.minValue.field || options.minValue.field === 'count'"
:placeholder="$t('progress.aggregate.select')"
:options="aggregationOperations"
:reduce="a => a.operation"
class="bg-white"
/>
</b-form-group>
</b-col>
</template>
</b-row>
</template>
<hr>
<template>
<h5 class="text-primary">
{{ $t('progress.value.max') }}
</h5>
<b-row>
<b-col
v-if="!options.maxValue.moduleID"
cols="12"
>
<b-form-group
:label="$t('progress.value.default.label')"
:description="$t('progress.value.default.description')"
>
<b-form-input
v-model="options.maxValue.default"
type="number"
number
/>
</b-form-group>
</b-col>
<b-col
cols="12"
>
@@ -307,60 +443,66 @@
</b-col>
</b-row>
<!-- Don't remove, makes sure preview doesn't cover content -->
<div class="w-501 my-5 py-5 py-sm-3" />
<hr>
<template>
<div
class="preview bg-white position-absolute p-3"
>
<h6 class="text-primary">
{{ $t('progress.preview') }}
</h6>
<h6 class="text-primary">
{{ $t('progress.preview') }}
</h6>
<b-progress
:max="preview.max"
height="1.5rem"
class="mb-2 bg-light"
<b-row>
<b-col
cols="12"
>
<b-progress-bar
:value="preview.value"
:striped="options.display.striped"
:animated="options.display.animated"
:variant="progressVariant"
>
{{ progressLabel }}
</b-progress-bar>
</b-progress>
<field-viewer
value-only
v-bind="mock"
class="mb-2"
/>
</b-col>
<b-row>
<b-col
cols="12"
sm="6"
>
{{ $t('progress.value.label') }}
<b-form-input
v-model="preview.value"
:placeholder="$t('progress.value.label')"
type="number"
size="sm"
/>
</b-col>
<b-col
cols="12"
sm="4"
>
{{ $t('progress.value.label') }}
<b-form-input
v-model="mock.record.values.mockField"
:placeholder="$t('progress.value.label')"
size="sm"
type="number"
number
/>
</b-col>
<b-col
cols="12"
sm="6"
>
{{ $t('progress.value.max') }}
<b-form-input
v-model="preview.max"
:placeholder="$t('progress.value.max')"
type="number"
size="sm"
/>
</b-col>
</b-row>
</div>
<b-col
cols="12"
sm="4"
>
{{ $t('progress.value.min') }}
<b-form-input
v-model="mock.field.options.min"
:placeholder="$t('progress.value.min')"
size="sm"
type="number"
number
/>
</b-col>
<b-col
cols="12"
sm="4"
>
{{ $t('progress.value.max') }}
<b-form-input
v-model="mock.field.options.max"
:placeholder="$t('progress.value.max')"
size="sm"
type="number"
number
/>
</b-col>
</b-row>
</template>
</template>
</b-tab>
@@ -369,7 +511,9 @@
<script>
import base from './base'
import { mapGetters } from 'vuex'
import { compose, validator } from '@cortezaproject/corteza-js'
import { VueSelect } from 'vue-select'
import FieldViewer from '../ModuleFields/Viewer'
export default {
i18nOptions: {
@@ -380,17 +524,13 @@ export default {
components: {
VueSelect,
FieldViewer,
},
extends: base,
data () {
return {
preview: {
value: 15,
max: 50,
},
aggregationOperations: [
{
name: this.$t('metric.edit.operationSum'),
@@ -420,6 +560,14 @@ export default {
{ text: this.$t('progress.variant.light'), value: 'light' },
{ text: this.$t('progress.variant.dark'), value: 'dark' },
],
mock: {
namespace: undefined,
module: undefined,
field: undefined,
record: undefined,
errors: new validator.Validated(),
},
}
},
@@ -431,61 +579,58 @@ export default {
sharedModuleFields () {
return [
{ name: 'count', label: 'Count' },
{ name: 'count', label: this.$t('progress.count') },
]
},
valueModuleFields () {
return [
...this.sharedModuleFields,
...this.moduleByID(this.options.value.moduleID).fields.filter(f => f.kind === 'Number').sort((a, b) => a.name.localeCompare(b.name)),
...this.moduleByID(this.options.value.moduleID).fields.filter(f => f.kind === 'Number').sort((a, b) => a.label.localeCompare(b.label)),
]
},
minValueModuleFields () {
return [
...this.sharedModuleFields,
...this.moduleByID(this.options.minValue.moduleID).fields.filter(f => f.kind === 'Number').sort((a, b) => a.label.localeCompare(b.label)),
]
},
maxValueModuleFields () {
return [
...this.sharedModuleFields,
...this.moduleByID(this.options.maxValue.moduleID).fields.filter(f => f.kind === 'Number').sort((a, b) => a.name.localeCompare(b.name)),
...this.moduleByID(this.options.maxValue.moduleID).fields.filter(f => f.kind === 'Number').sort((a, b) => a.label.localeCompare(b.label)),
]
},
},
progress () {
const { value = 0, max = 100 } = this.preview
return 100 * (value / max)
watch: {
options: {
deep: true,
handler ({ display = {} }) {
if (this.mock.field) {
this.mock.field.options = {
...this.mock.field.options,
...display,
}
}
},
},
},
progressLabel () {
let { value, max } = this.preview
const { showValue, showRelative, showProgress } = this.options.display || {}
if (!showValue) {
return
}
if (showRelative) {
// https://stackoverflow.com/a/21907972/17926309
value = `${Math.round(((value / max) * 100) * 100) / 100}%`
}
if (showProgress) {
value = `${value} / ${showRelative ? '100' : max}${showRelative ? '%' : ''}`
}
return value
},
progressVariant () {
const { variant } = this.options.display || {}
let progressVariant = variant
if (this.options.display.thresholds.length) {
const sortedVariants = [...this.options.display.thresholds].filter(t => t.value >= 0).sort((a, b) => b.value - a.value)
const { variant } = sortedVariants.find(t => this.progress >= t.value) || {}
progressVariant = variant || progressVariant
}
return progressVariant
},
created () {
this.mock.namespace = this.namespace
this.mock.field = compose.ModuleFieldMaker({ kind: 'Number' })
this.mock.field.apply({ name: 'mockField' })
this.mock.field.options.display = 'progress'
this.mock.field.options = {
display: 'progress',
...this.mock.field.options,
...this.options.display,
}
this.mock.module = new compose.Module({ fields: [this.mock.field] }, this.namespace)
this.mock.record = new compose.Record(this.mock.module, { mockField: 15 })
},
methods: {
@@ -500,7 +645,7 @@ export default {
},
fieldChanged (value, optionsType) {
if (!value) {
if (!value || value === 'count') {
optionsType.operation = ''
}
},

View File

@@ -17,6 +17,7 @@ interface NumberOptions extends Options {
precision: number;
multiDelimiter: string;
display: string;
min: number;
max: number;
showValue: boolean;
showRelative: boolean;
@@ -36,6 +37,7 @@ const defaults = (): Readonly<NumberOptions> => Object.freeze({
prefix: '',
suffix: '',
// Progress bar display options
min: 0,
max: 100,
showValue: true,
showRelative: true,
@@ -60,7 +62,7 @@ export class ModuleFieldNumber extends ModuleField {
super.applyOptions(o)
Apply(this.options, o, String, 'format', 'prefix', 'suffix', 'multiDelimiter', 'display', 'variant')
Apply(this.options, o, Number, 'precision', 'max')
Apply(this.options, o, Number, 'precision', 'min', 'max')
Apply(this.options, o, Boolean, 'showValue', 'showRelative', 'showProgress', 'animated')
if (o.thresholds) {

View File

@@ -1,3 +1,4 @@
import _ from 'lodash'
import { PageBlock, PageBlockInput, Registry } from './base'
import { dimensionFunctions } from '../chart/util'
import { Compose as ComposeAPI } from '../../../api-clients'
@@ -6,6 +7,7 @@ import { Apply } from '../../../cast'
const kind = 'Progress'
interface ValueOptions {
default: number;
moduleID: string;
filter: string;
field: string;
@@ -28,6 +30,7 @@ interface DisplayOptions {
interface Options {
value: ValueOptions;
minValue: ValueOptions;
maxValue: ValueOptions;
display: DisplayOptions;
refreshRate: number;
@@ -35,6 +38,15 @@ interface Options {
const defaults: Readonly<Options> = Object.freeze({
value: {
default: 0,
moduleID: '',
filter: '',
field: '',
operation: '',
},
minValue: {
default: 0,
moduleID: '',
filter: '',
field: '',
@@ -42,6 +54,7 @@ const defaults: Readonly<Options> = Object.freeze({
},
maxValue: {
default: 100,
moduleID: '',
filter: '',
field: '',
@@ -75,15 +88,19 @@ export class PageBlockProgress extends PageBlock {
Apply(this.options, o, Number, 'refreshRate')
if (o.value) {
this.options.value = o.value
this.options.value = { ...this.options.value, ...o.value }
}
if (o.minValue) {
this.options.minValue = { ...this.options.minValue, ...o.minValue }
}
if (o.maxValue) {
this.options.maxValue = o.maxValue
this.options.maxValue = { ...this.options.maxValue, ...o.maxValue }
}
if (o.display) {
this.options.display = o.display
this.options.display = { ...this.options.display, ...o.display }
}
}
@@ -91,52 +108,94 @@ export class PageBlockProgress extends PageBlock {
* Helper function to fetch and parse reporter's reports.
*/
fetch (options: Options, api: ComposeAPI, namespaceID: string): Promise<object> {
options = _.merge(this.options, options)
const reports = []
const dimensions = dimensionFunctions.convert({ modifier: 'YEAR', field: 'createdAt' })
let metrics = ''
// Construct value report
const { field: valueField, operation: valueOperation = '' } = this.options.value
if (valueOperation && valueField && valueField !== 'count') {
metrics = `${valueOperation}(${valueField}) AS rp`
}
const valueReport = api.recordReport({ namespaceID, metrics, dimensions, ...this.options.value, ...options.value })
// Construct max value report
metrics = ''
if (options.value.moduleID && valueField) {
if (valueOperation && valueField !== 'count') {
metrics = `${valueOperation}(${valueField}) AS rp`
}
reports.push(api.recordReport({ namespaceID, metrics, dimensions, ...options.value }))
} else {
reports.push(new Promise(resolve => resolve(options.value.default)))
}
// Construct minValue report
const { field: minValueField, operation: minValueOperation = '' } = this.options.minValue
if (options.minValue.moduleID && minValueField) {
metrics = ''
if (minValueOperation && minValueField !== 'count') {
metrics = `${minValueOperation}(${minValueField}) AS rp`
}
reports.push(api.recordReport({ namespaceID, metrics, dimensions, ...options.minValue }))
} else {
reports.push(new Promise(resolve => resolve(options.minValue.default)))
}
// Construct minValue report
const { field: maxValueField, operation: maxValueOperation = '' } = this.options.maxValue
if (maxValueOperation && maxValueField && maxValueField !== 'count') {
metrics = `${maxValueOperation}(${maxValueField}) AS rp`
if (options.maxValue.moduleID && maxValueField) {
metrics = ''
if (maxValueOperation && maxValueField !== 'count') {
metrics = `${maxValueOperation}(${maxValueField}) AS rp`
}
reports.push(api.recordReport({ namespaceID, metrics, dimensions, ...options.maxValue }))
} else {
reports.push(new Promise(resolve => resolve(options.maxValue.default)))
}
const maxValueReport = api.recordReport({ namespaceID, metrics, dimensions, ...this.options.maxValue, ...options.maxValue })
return Promise.all([valueReport, maxValueReport]).then(([v, m]: Array<any>) => {
let value: number
let max: number
let datasets = v.map((r: any) => r.rp !== undefined ? r.rp : r.count)
if (valueOperation === 'max') {
value = datasets.sort((a: number, b: number) => b - a)[0]
} else if (valueOperation === 'min') {
value = datasets.sort((a: number, b: number) => a - b)[0]
} else if (valueOperation === 'avg') {
value = datasets.reduce((acc: number, cur: number) => acc + cur, 0) / datasets.length
} else {
value = datasets.reduce((acc: number, cur: number) => acc + cur, 0)
return Promise.all(reports).then(([value, min, max]: Array<any>) => {
if (Array.isArray(value)) {
const datasets = value.map((r: any) => r.rp !== undefined ? r.rp : r.count)
if (valueOperation === 'max') {
value = datasets.sort((a: number, b: number) => b - a)[0]
} else if (valueOperation === 'min') {
value = datasets.sort((a: number, b: number) => a - b)[0]
} else if (valueOperation === 'avg') {
value = datasets.reduce((acc: number, cur: number) => acc + cur, 0) / datasets.length
} else {
value = datasets.reduce((acc: number, cur: number) => acc + cur, 0)
}
}
datasets = m.map((r: any) => r.rp !== undefined ? r.rp : r.count)
if (maxValueOperation === 'max') {
max = datasets.sort((a: number, b: number) => b - a)[0]
} else if (maxValueOperation === 'min') {
max = datasets.sort((a: number, b: number) => a - b)[0]
} else if (maxValueOperation === 'avg') {
max = datasets.reduce((acc: number, cur: number) => acc + cur, 0) / datasets.length
} else {
max = datasets.reduce((acc: number, cur: number) => acc + cur, 0)
if (Array.isArray(min)) {
const datasets = min.map((r: any) => r.rp !== undefined ? r.rp : r.count)
if (minValueOperation === 'max') {
min = datasets.sort((a: number, b: number) => b - a)[0]
} else if (minValueOperation === 'min') {
min = datasets.sort((a: number, b: number) => a - b)[0]
} else if (minValueOperation === 'avg') {
min = datasets.reduce((acc: number, cur: number) => acc + cur, 0) / datasets.length
} else {
min = datasets.reduce((acc: number, cur: number) => acc + cur, 0)
}
}
return { value, max }
if (Array.isArray(max)) {
const datasets = max.map((r: any) => r.rp !== undefined ? r.rp : r.count)
if (maxValueOperation === 'max') {
max = datasets.sort((a: number, b: number) => b - a)[0]
} else if (maxValueOperation === 'min') {
max = datasets.sort((a: number, b: number) => a - b)[0]
} else if (maxValueOperation === 'avg') {
max = datasets.reduce((acc: number, cur: number) => acc + cur, 0) / datasets.length
} else {
max = datasets.reduce((acc: number, cur: number) => acc + cur, 0)
}
}
return { value, min, max }
})
}
}

View File

@@ -77,4 +77,8 @@ export {
CChart,
} from './chart'
export {
CProgress,
} from './progress'
export { default as C3 } from './c3'

View File

@@ -0,0 +1,128 @@
<template>
<b-progress
:max="maxValue"
class="bg-light position-relative"
>
<b-progress-bar
:value="progressValue < 0 ? 0 : progressValue"
:striped="striped"
:animated="animated"
:variant="progressVariant"
>
<strong
:class="textVariant"
class="d-flex align-items-center justify-content-center position-absolute mb-0 w-100"
>
{{ progressLabel }}
</strong>
</b-progress-bar>
</b-progress>
</template>
<script>
export default {
props: {
value: {
type: Number,
},
min: {
type: Number,
default: 0,
},
max: {
type: Number,
default: 100,
},
labeled: {
type: Boolean,
default: true,
},
relative: {
type: Boolean,
default: true,
},
progress: {
type: Boolean,
},
striped: {
type: Boolean,
},
animated: {
type: Boolean,
},
variant: {
type: String,
default: 'success',
},
thresholds: {
type: Array,
default: () => [],
},
},
computed: {
maxValue () {
return Math.abs(this.max - this.min)
},
progressValue () {
if (this.value < this.min && this.max > this.min) {
return this.value - this.min
} else if (this.value > this.min && this.max < this.min) {
return this.min - this.value
}
return Math.abs(this.value - this.min)
},
progressLabel () {
let value = this.value
if (!this.labeled) {
return
}
if (this.relative) {
// https://stackoverflow.com/a/21907972/17926309
value = `${Math.round((((this.progressValue) / this.maxValue) * 100) * 100) / 100}%`
}
if (this.progress) {
value = `${value} / ${this.relative ? '100' : this.max}${this.relative ? '%' : ''}`
}
return value
},
progressVariant () {
const value = Math.round((((this.progressValue < 0 ? 0 : this.progressValue) / this.maxValue) * 100) * 100) / 100
let progressVariant = this.variant
if (this.thresholds.length) {
const { variant } = this.sortedVariants.find(t => value >= t.value) || {}
progressVariant = variant || progressVariant
}
return progressVariant
},
sortedVariants () {
return [...this.thresholds].filter(t => t.value >= 0).sort((a, b) => b.value - a.value)
},
textVariant () {
return ['dark', 'primary'].includes(this.progressVariant) ? 'text-white' : 'text-dark'
},
},
}
</script>

View File

@@ -0,0 +1 @@
export { default as CProgress } from './CProgress.vue'

View File

@@ -491,6 +491,7 @@ report:
progress:
add: + Add
count: Count
aggregate:
label: Aggregation operation
select: Select aggregation operation
@@ -518,7 +519,11 @@ progress:
variant: Set which variant to show if value is equal or larger than the threshold
value:
label: Value
min: Minimum value
max: Maximum value
default:
label: Default value
description: This value will be used if metric is not configured
variant:
default: Dark
danger: Danger

View File

@@ -52,11 +52,11 @@ kind:
exampleFormat: Format
exampleInput: Input
exampleResult: Result
examplesLabel: 'Format examples:'
examplesLabel: Format examples
formatLabel: Format
formatPlaceholder: Format
label: Number
liveExample: 'Live example:'
liveExample: Live example
precisionLabel: Precision
precisionPlaceholder: Precision
prefixLabel: Prefix
@@ -67,8 +67,9 @@ kind:
label: Display type
number: Number
progress: Progress bar
maximumValue: Maximum value
progress:
maximumValue: Maximum value
minimumValue: Minimum value
animated: Animated
show:
value: Show value