3
0

Add auto-refresh to page blocks

This commit is contained in:
Emmy Leke
2022-11-25 17:10:40 +01:00
parent fda4730a46
commit de905d27fe
30 changed files with 298 additions and 92 deletions

View File

@@ -2,6 +2,7 @@
<wrap
v-bind="$props"
v-on="$listeners"
@refreshBlock="refresh"
>
<div class="d-flex flex-column calendar-container p-2 h-100">
<div v-if="!header.hide">
@@ -87,6 +88,7 @@
<full-calendar
v-show="show && !processing"
ref="fc"
:key="key"
:height="getHeight()"
:events="events"
v-bind="config"
@@ -155,6 +157,8 @@ export default {
start: null,
end: null,
},
refreshing: false,
}
},
@@ -223,6 +227,7 @@ export default {
created () {
this.changeLocale(this.currentLanguage)
this.refreshBlock(this.refresh, true)
},
methods: {
@@ -268,7 +273,7 @@ export default {
return
}
if (start.isSame(this.loaded.start) && end.isSame(this.loaded.end)) {
if (start.isSame(this.loaded.start) && end.isSame(this.loaded.end) && !this.refreshing) {
return
}
@@ -313,6 +318,7 @@ export default {
}))
.finally(() => {
this.processing = false
this.refreshing = false
setTimeout(() => {
this.updateSize()
})
@@ -342,6 +348,11 @@ export default {
}
return 'auto'
},
refresh () {
this.refreshing = true
this.api().refetchEvents()
},
},
}
</script>

View File

@@ -2,9 +2,11 @@
<wrap
v-bind="$props"
v-on="$listeners"
@refreshBlock="refresh"
>
<chart-component
v-if="chart"
:key="key"
:chart="chart"
:record="record"
:reporter="reporter"
@@ -35,16 +37,8 @@ export default {
},
mounted () {
const { chartID } = this.options
if (chartID === NoID) {
return
}
const { namespaceID } = this.namespace
this.findChartByID({ chartID, namespaceID }).then((chart) => {
this.chart = chart
}).catch(this.toastErrorHandler(this.$t('chart.loadFailed')))
this.fetchChart()
this.refreshBlock(this.refresh, true)
},
methods: {
@@ -52,6 +46,20 @@ export default {
findChartByID: 'chart/findByID',
}),
fetchChart (params = {}) {
const { chartID } = this.options
if (chartID === NoID) {
return
}
const { namespaceID } = this.namespace
this.findChartByID({ chartID, namespaceID, ...params }).then((chart) => {
this.chart = chart
}).catch(this.toastErrorHandler(this.$t('chart.loadFailed')))
},
reporter (r) {
const nr = { ...r }
if (nr.filter) {
@@ -72,6 +80,11 @@ export default {
const { namespaceID } = this.namespace
return this.$ComposeAPI.recordReport({ namespaceID, ...nr })
},
refresh () {
this.fetchChart({ force: true })
},
},
}
</script>

View File

@@ -2,6 +2,7 @@
<wrap
v-bind="$props"
v-on="$listeners"
@refreshBlock="refresh"
>
<div
v-if="processing"
@@ -205,11 +206,15 @@ export default {
'record.recordID': {
immediate: true,
handler () {
this.reloadRecords()
this.refresh()
},
},
},
created () {
this.refreshBlock(this.refresh)
},
methods: {
getFormattedDate (date) {
return fmt.fullDateTime(date)
@@ -227,7 +232,7 @@ export default {
this.$store.dispatch('user/fetchUsers', userListID)
},
reloadRecords () {
refresh () {
if (!this.options.moduleID) {
// Make sure block is properly configured
throw Error(this.$t('record.moduleOrPageNotSet'))
@@ -277,7 +282,7 @@ export default {
// clean the input and reload data
this.newRecord.title = ''
this.newRecord.content = ''
this.reloadRecords()
this.refresh()
})
.catch(this.toastErrorHandler(this.$t('notification:record.createFailed')))
}

View File

@@ -69,6 +69,7 @@
</b-input-group-append>
</b-input-group>
</b-form-group>
<b-form-group
for="color"
:label="$t('general.headerStyle')"
@@ -80,14 +81,38 @@
/>
</b-form-group>
<b-form-checkbox
v-model="block.style.wrap.kind"
value="card"
unchecked-value="plain"
switch
<b-form-group>
<b-form-checkbox
v-model="block.style.wrap.kind"
value="card"
unchecked-value="plain"
switch
>
{{ $t('general.wrap') }}
</b-form-checkbox>
</b-form-group>
<b-form-group
v-if="block.options.refreshRate !== undefined"
:label="$t('general.refresh.label')"
:description="$t('general.refresh.description')"
>
{{ $t('general.wrap') }}
</b-form-checkbox>
<b-col
cols="12"
sm="3"
class="pl-0"
>
<b-input-group append="s">
<b-form-input
v-model="block.options.refreshRate"
type="number"
number
min="0"
@blur="updateRefresh"
/>
</b-input-group>
</b-col>
</b-form-group>
</div>
</b-tab>
@@ -148,6 +173,13 @@ export default {
return this.block.blockID === NoID
},
},
methods: {
updateRefresh (e) {
// If value is less than 5 but greater than 0 make it 5. Otherwise value stays the same.
this.block.options.refreshRate = e.target.value < 5 && e.target.value > 0 ? 5 : e.target.value
},
},
}
</script>
<style scoped>

View File

@@ -2,9 +2,11 @@
<wrap
v-bind="$props"
v-on="$listeners"
@refreshBlock="refresh"
>
<iframe
v-if="src"
ref="iframe"
class="h-100 w-100 border-0"
:src="src"
/>
@@ -30,5 +32,15 @@ export default {
return src || blank
},
},
mounted () {
this.refreshBlock(this.refresh)
},
methods: {
refresh () {
this.$refs.iframe.src = this.src
},
},
}
</script>

View File

@@ -2,6 +2,7 @@
<wrap
v-bind="$props"
v-on="$listeners"
@refreshBlock="refresh"
>
<div
v-if="processing"
@@ -61,7 +62,6 @@ export default {
data () {
return {
processing: false,
reports: [],
}
},
@@ -70,21 +70,25 @@ export default {
'record.recordID': {
immediate: true,
handler () {
this.update()
this.refresh()
},
},
},
mounted () {
this.$root.$on('metric.update', this.update)
this.$root.$on(`refetch-non-record-blocks:${this.page.pageID}`, this.update)
this.$root.$on('metric.update', this.refresh)
this.$root.$on(`refetch-non-record-blocks:${this.page.pageID}`, this.refresh)
},
beforeDestroy () {
this.$root.$off('metric.update', this.update)
this.$root.$off('metric.update', this.refresh)
this.$root.$off(`refetch-non-record-blocks:${this.page.pageID}`)
},
created () {
this.refreshBlock(this.refresh)
},
methods: {
/**
* Performs some post processing on the provided data
@@ -114,7 +118,7 @@ export default {
/**
* Pulls fresh data from the API
*/
async update () {
async refresh () {
this.processing = true
try {
@@ -146,6 +150,7 @@ export default {
this.processing = false
}
},
},
}
</script>

View File

@@ -2,6 +2,7 @@
<wrap
v-bind="$props"
v-on="$listeners"
@refreshBlock="refresh"
>
<div
v-if="processing"
@@ -46,7 +47,6 @@ export default {
data () {
return {
processing: false,
value: undefined,
max: undefined,
}
@@ -99,13 +99,17 @@ export default {
'record.recordID': {
immediate: true,
handler () {
this.update()
this.refresh()
},
},
},
created () {
this.refreshBlock(this.refresh)
},
mounted () {
this.$root.$on(`refetch-non-record-blocks:${this.page.pageID}`, this.update)
this.$root.$on(`refetch-non-record-blocks:${this.page.pageID}`, this.refresh)
},
beforeDestroy () {
@@ -116,7 +120,7 @@ export default {
/**
* Pulls fresh data from the API
*/
async update () {
async refresh () {
this.processing = true
const { namespaceID } = this.namespace || {}

View File

@@ -25,7 +25,7 @@
{{ field.label || field.name }}
<hint
:id="field.fieldID"
:text="(field.options.hint || {}).view || ''"
:text="((field.options.hint || {}).view || '')"
class="d-inline-block"
/>
</label>

View File

@@ -34,7 +34,7 @@
{{ field.label || field.name }}
<hint
:id="field.fieldID"
:text="(field.options.hint || {}).view || ''"
:text="((field.options.hint || {}).view || '')"
class="d-inline-block"
/>
</label>

View File

@@ -4,30 +4,18 @@
v-bind="$props"
:scrollable-body="false"
v-on="$listeners"
@refreshBlock="refresh"
>
<template
v-if="showHeader"
#header
v-if="isFederated"
#title-badge
>
<h5
class="d-flex align-items-center text-truncate mb-0"
<b-badge
variant="primary"
class="d-inline-block mb-0 ml-2"
>
{{ block.title }}
<b-badge
v-if="isFederated"
variant="primary"
class="d-inline-block mb-0 ml-2"
>
{{ $t('recordList.federated') }}
</b-badge>
</h5>
<b-card-text
v-if="block.description"
class="text-dark text-truncate mt-1"
>
{{ block.description }}
</b-card-text>
{{ $t('recordList.federated') }}
</b-badge>
</template>
<template #toolbar>
@@ -688,10 +676,6 @@ export default {
return Object.keys(this.recordListModule.labels || {}).includes('federation')
},
showHeader () {
return !!(this.block.title || this.block.description || this.isFederated)
},
showFooter () {
return !this.options.hidePaging && !this.inlineEditing
},
@@ -878,6 +862,12 @@ export default {
this.$root.$off(`refetch-non-record-blocks:${this.page.pageID}`)
},
created () {
if (!this.inlineEditing) {
this.refreshBlock(this.refresh)
}
},
methods: {
onFilter (filter = []) {
this.recordListFilter = filter
@@ -1288,6 +1278,7 @@ export default {
let paginationOptions = {}
if (resetPagination) {
this.filter.pageCursor = undefined
const { fullPageNavigation = false, showTotalCount = false } = this.options
paginationOptions = {
incPageNavigation: fullPageNavigation,
@@ -1300,7 +1291,6 @@ export default {
const records = set.map(r => new compose.Record(r, this.recordListModule))
this.filter = { ...this.filter, ...filter }
this.filter.pageCursor = undefined
this.filter.nextPage = filter.nextPage
this.filter.prevPage = filter.prevPage

View File

@@ -231,26 +231,15 @@ export default {
throw Error(this.$t('notification:record.moduleOrPageNotSet'))
}
if (this.roModule) {
this.processing = true
this.fetchRecords(this.roModule, this.expandFilter())
.then(rr => {
this.records = rr
const fields = [this.labelField, this.descriptionField].filter(f => !!f)
this.fetchUsers(fields, this.records)
})
.catch(e => {
console.error(e)
})
.finally(() => {
this.processing = false
})
}
this.refresh()
},
},
},
created () {
this.refreshBlock(this.refresh)
},
methods: {
// Allow move if repositioned or if record isn't in target record organizer
checkMove ({ draggedContext = {}, relatedContext = {} }) {
@@ -421,6 +410,26 @@ export default {
return this.$ComposeAPI.recordList({ namespaceID, moduleID, query, sort })
.then(({ set }) => set.map(r => Object.freeze(new compose.Record(module, r))))
},
refresh () {
if (this.roModule) {
this.processing = true
this.fetchRecords(this.roModule, this.expandFilter())
.then(rr => {
this.records = rr
const fields = [this.labelField, this.descriptionField].filter(f => !!f)
this.fetchUsers(fields, this.records)
})
.catch(e => {
console.error(e)
})
.finally(() => {
this.processing = false
})
}
},
},
}
</script>

View File

@@ -3,6 +3,7 @@
v-bind="$props"
:scrollable-body="true"
v-on="$listeners"
@refreshBlock="refresh"
>
<div
class="d-flex flex-column align-items-center h-100 overflow-hidden"
@@ -20,7 +21,7 @@
<b-button
v-else-if="!preloadRevisions && !loadedRevisions"
class="my-auto"
@click="loadRevisions()"
@click="refresh()"
>
{{ $t('show-revisions', { revision: record.revision }) }}
</b-button>
@@ -198,8 +199,12 @@ export default {
},
},
created () {
this.refreshBlock(this.refresh)
},
methods: {
async refresh () {
async loadRevisions () {
if (this.revisionsDisabledOnModule) {
return
}
@@ -221,9 +226,9 @@ export default {
})
},
loadRevisions () {
refresh () {
this.loadedRevisions = true
this.refresh()
this.loadedRevisions()
},
},
}

View File

@@ -12,6 +12,7 @@
<display-element
v-else-if="displayElement"
:key="key"
:display-element="displayElement"
:labels="{
previous: $t('recordList.pagination.prev'),
@@ -38,7 +39,6 @@ export default {
data () {
return {
processing: false,
report: undefined,
displayElement: undefined,
}
@@ -56,6 +56,10 @@ export default {
},
},
created () {
this.refreshBlock(this.refresh, true)
},
methods: {
fetchReport (reportID) {
this.processing = true
@@ -129,6 +133,10 @@ export default {
return scenarioDefinition
},
refresh () {
this.fetchReport(this.options.reportID)
},
},
}
</script>

View File

@@ -2,6 +2,7 @@
<wrap
v-bind="$props"
v-on="$listeners"
@refreshBlock="key++"
>
<div
v-if="profile"
@@ -10,6 +11,7 @@
<timeline
v-if="isTwitter"
:id="profile.twitterHandle"
:key="key"
class="h-100"
:options="{ tweetLimit: 9 }"
source-type="profile"
@@ -54,6 +56,10 @@ export default {
},
},
mounted () {
this.refreshBlock(() => {}, true)
},
methods: {
getTwitterHandle (url) {
const twitterUnpacked = url.split('/')

View File

@@ -15,12 +15,26 @@
<div
v-if="!headerSet"
>
<h5
<div
v-if="block.title"
class="text-truncate mb-0"
class="d-flex justify-content-between align-items-center"
>
{{ block.title }}
</h5>
<h5
class="text-truncate mb-0"
>
{{ block.title }}
<slot name="title-badge" />
</h5>
<font-awesome-icon
v-if="block.options.refreshRate >= 5"
:icon="['fa', 'sync']"
class="h6 text-secondary mb-0"
role="button"
@click="$emit('refreshBlock')"
/>
</div>
<b-card-text
v-if="block.description"

View File

@@ -11,12 +11,26 @@
<div
v-if="!headerSet"
>
<h5
<div
v-if="block.title"
class="text-truncate mb-0"
class="d-flex justify-content-between align-items-center"
>
{{ block.title }}
</h5>
<h5
class="text-truncate mb-0"
>
{{ block.title }}
<slot name="title-badge" />
</h5>
<font-awesome-icon
v-if="block.options.refreshRate >= 5"
:icon="['fa', 'sync']"
class="h6 text-secondary mb-0"
role="button"
@click="$emit('refreshBlock')"
/>
</div>
<b-card-text
v-if="block.description"

View File

@@ -57,10 +57,50 @@ export default {
},
},
data () {
return {
refreshInterval: null,
key: 0,
}
},
computed: {
options () {
return this.block.options
},
allowsRefresh () {
return this.options.refreshRate >= 5 && ['page', 'page.record'].includes(this.$route.name)
},
},
beforeDestroy () {
clearInterval(this.refreshInterval)
},
methods: {
/**
*
* @param {*} refreshFunction
* @param {*} forceRerender
* Key is used to force a component to re-render
* However, sometimes reloading data is enough
* Attach :key="key" if you need to re-render a component
*
*/
refreshBlock (refreshFunction, forceRerender) {
if (!this.allowsRefresh || this.refreshInterval) return
const interval = setInterval(() => {
refreshFunction()
if (forceRerender) {
this.key++
}
}, this.options.refreshRate * 1000)
this.refreshInterval = interval
},
},
}
</script>

View File

@@ -58,6 +58,7 @@ import {
faMapMarkedAlt,
faKey,
faQuestion,
faSync,
} from '@fortawesome/free-solid-svg-icons'
import {
@@ -162,4 +163,5 @@ library.add(
faMapMarkedAlt,
faKey,
faQuestion,
faSync,
)

View File

@@ -3,6 +3,7 @@ import { PageBlock, Registry } from '../base'
import Feed, { FeedInput } from './feed'
import { ReminderFeed } from './feed-reminder'
import { RecordFeed } from './feed-record'
import { Apply } from '../../../../cast'
const kind = 'Calendar'
@@ -34,6 +35,7 @@ class CalendarOptions {
public feeds: Array<Feed> = []
public header: Partial<CalendarOptionsHeader> = {}
public locale = 'en-gb'
public refreshRate = 0
}
/**
@@ -55,7 +57,7 @@ export class PageBlockCalendar extends PageBlock {
applyOptions (o?: Partial<CalendarOptions>): void {
if (!o) return
Apply(this.options, o, Number, 'refreshRate')
this.options.defaultView = PageBlockCalendar.handleLegacyView(o.defaultView) || 'dayGridMonth'
this.options.feeds = (o.feeds || []).map(f => new Feed(f))
this.options.header = merge(

View File

@@ -5,10 +5,12 @@ const kind = 'Chart'
interface Options {
chartID: string;
refreshRate: number;
}
const defaults: Readonly<Options> = Object.freeze({
chartID: NoID,
refreshRate: 0,
})
export class PageBlockChart extends PageBlock {
@@ -25,6 +27,7 @@ export class PageBlockChart extends PageBlock {
if (!o) return
Apply(this.options, o, CortezaID, 'chartID')
Apply(this.options, o, Number, 'refreshRate')
}
}

View File

@@ -9,6 +9,7 @@ interface Options {
contentField: string;
referenceField: string;
sortDirection: string;
refreshRate: number;
}
const defaults: Readonly<Options> = Object.freeze({
@@ -18,6 +19,7 @@ const defaults: Readonly<Options> = Object.freeze({
contentField: '',
sortDirection: '',
referenceField: '',
refreshRate: 0,
})
export class PageBlockComment extends PageBlock {
@@ -34,6 +36,7 @@ export class PageBlockComment extends PageBlock {
if (!o) return
Apply(this.options, o, CortezaID, 'moduleID')
Apply(this.options, o, String, 'titleField', 'contentField', 'referenceField', 'filter', 'sortDirection')
Apply(this.options, o, Number, 'refreshRate')
}
}

View File

@@ -7,12 +7,14 @@ interface Options {
srcField: string;
src: string;
wrap: PageBlockWrap;
refreshRate: number;
}
const defaults: Readonly<Options> = Object.freeze({
srcField: '',
src: '',
wrap: 'Plain',
refreshRate: 0,
})
export class PageBlockIFrame extends PageBlock {
@@ -29,6 +31,7 @@ export class PageBlockIFrame extends PageBlock {
if (!o) return
Apply(this.options, o, String, 'srcField', 'src', 'wrap')
Apply(this.options, o, Number, 'refreshRate')
}
}

View File

@@ -1,6 +1,6 @@
import { PageBlock, PageBlockInput, Registry } from './base'
import { dimensionFunctions } from '../chart/util'
import { CortezaID } from '../../../cast'
import { CortezaID, Apply } from '../../../cast'
const kind = 'Metric'
@@ -40,10 +40,12 @@ interface Metric {
interface Options {
metrics: Array<Metric>;
refreshRate: number;
}
const defaults: Readonly<Options> = Object.freeze({
metrics: [],
refreshRate: 0,
})
export class PageBlockMetric extends PageBlock {
@@ -58,7 +60,7 @@ export class PageBlockMetric extends PageBlock {
applyOptions (o?: Partial<Options>): void {
if (!o) return
Apply(this.options, o, Number, 'refreshRate')
if (o.metrics) {
this.options.metrics = o.metrics
}

View File

@@ -1,6 +1,7 @@
import { PageBlock, PageBlockInput, Registry } from './base'
import { dimensionFunctions } from '../chart/util'
import { Compose as ComposeAPI } from '../../../api-clients'
import { Apply } from '../../../cast'
const kind = 'Progress'
@@ -29,6 +30,7 @@ interface Options {
value: ValueOptions;
maxValue: ValueOptions;
display: DisplayOptions;
refreshRate: number;
}
const defaults: Readonly<Options> = Object.freeze({
@@ -54,6 +56,7 @@ const defaults: Readonly<Options> = Object.freeze({
variant: 'success',
thresholds: [],
},
refreshRate: 0,
})
export class PageBlockProgress extends PageBlock {
@@ -69,6 +72,8 @@ export class PageBlockProgress extends PageBlock {
applyOptions (o?: Partial<Options>): void {
if (!o) return
Apply(this.options, o, Number, 'refreshRate')
if (o.value) {
this.options.value = o.value
}

View File

@@ -27,6 +27,7 @@ interface Options {
fullPageNavigation: boolean;
showTotalCount: boolean;
refreshRate: number;
// Record-lines
editable: boolean;
@@ -87,6 +88,7 @@ const defaults: Readonly<Options> = Object.freeze({
selectMode: 'multi',
selectionButtons: [],
refreshRate: 0,
})
export class PageBlockRecordList extends PageBlock {
@@ -103,8 +105,8 @@ export class PageBlockRecordList extends PageBlock {
if (!o) return
Apply(this.options, o, CortezaID, 'moduleID')
Apply(this.options, o, String, 'prefilter', 'presort', 'selectMode', 'positionField', 'refField', 'recordDisplayOption')
Apply(this.options, o, Number, 'perPage')
Apply(this.options, o, String, 'prefilter', 'presort', 'selectMode', 'positionField', 'refField')
Apply(this.options, o, Number, 'perPage', 'refreshRate')
if (o.fields) {
this.options.fields = o.fields

View File

@@ -10,6 +10,7 @@ interface Options {
positionField: string;
groupField: string;
group: string;
refreshRate: number;
}
const defaults: Readonly<Options> = Object.freeze({
@@ -20,6 +21,7 @@ const defaults: Readonly<Options> = Object.freeze({
positionField: '',
groupField: '',
group: '',
refreshRate: 0,
})
export class PageBlockRecordOrganizer extends PageBlock {
@@ -37,6 +39,7 @@ export class PageBlockRecordOrganizer extends PageBlock {
Apply(this.options, o, CortezaID, 'moduleID')
Apply(this.options, o, String, 'labelField', 'descriptionField', 'filter', 'positionField', 'groupField', 'group')
Apply(this.options, o, Number, 'refreshRate')
}
}

View File

@@ -16,12 +16,14 @@ interface Options {
// referenced fields (records, users) we want to expand
expRefFields: string[];
refreshRate: number;
}
const defaults: Readonly<Options> = Object.freeze({
preload: false,
displayedFields: [],
expRefFields: [],
refreshRate: 0,
})
export class PageBlockRecordRevisions extends PageBlock {
@@ -38,6 +40,7 @@ export class PageBlockRecordRevisions extends PageBlock {
if (!o) return
Apply(this.options, o, Boolean, 'preload')
Apply(this.options, o, Number, 'refreshRate')
// set new values to displayed fields
if (Array.isArray(o?.displayedFields)) {

View File

@@ -7,12 +7,14 @@ interface Options {
reportID: string;
scenarioID: string;
elementID: string;
refreshRate: number;
}
const defaults: Readonly<Options> = Object.freeze({
reportID: NoID,
scenarioID: NoID,
elementID: NoID,
refreshRate: 0,
})
export class PageBlockReport extends PageBlock {
@@ -29,6 +31,7 @@ export class PageBlockReport extends PageBlock {
if (!o) return
Apply(this.options, o, CortezaID, 'reportID', 'scenarioID', 'elementID')
Apply(this.options, o, Number, 'refreshRate')
}
}

View File

@@ -7,6 +7,7 @@ interface Options {
fields: unknown[];
profileSourceField: string;
profileUrl: string;
refreshRate: number;
}
const defaults: Readonly<Options> = Object.freeze({
@@ -14,6 +15,7 @@ const defaults: Readonly<Options> = Object.freeze({
fields: [],
profileSourceField: '',
profileUrl: '',
refreshRate: 0,
})
export class PageBlockSocialFeed extends PageBlock {
@@ -31,6 +33,7 @@ export class PageBlockSocialFeed extends PageBlock {
Apply(this.options, o, CortezaID, 'moduleID')
Apply(this.options, o, String, 'profileSourceField', 'profileUrl')
Apply(this.options, o, Number, 'refreshRate')
if (o.fields) {
this.options.fields = o.fields

View File

@@ -108,6 +108,10 @@ general:
descriptionPlaceholder: Block description
headerStyle: Block header style (color)
wrap: Display block as a card
refresh:
label: Block auto-refresh interval
description: Setting value to less than 5 seconds disables auto-refresh
rate: seconds
label:
add: Add
back: Back