More performance improvements for the entities page.

- Don't recalculate entity groups every time. Instead, keep them in sync
  every time an entity is added or removed.

- Removed `computedChildren` from the entity component - no null nodes
  are guaranteed to be passed now, so there's no need for another
  iteration on the list of children.

- `childrenByParentId` now only looks in the scope of the entity's
  children instead of searching all the entities.
This commit is contained in:
Fabio Manganiello 2023-05-02 10:14:03 +02:00
parent 0fc0a22cd7
commit 68359b88a9
Signed by: blacklight
GPG Key ID: D90FBA7F76362774
2 changed files with 72 additions and 76 deletions

View File

@ -8,7 +8,7 @@
:is="component" :is="component"
:value="value" :value="value"
:parent="parent" :parent="parent"
:children="computedChildren" :children="children"
:loading="loading" :loading="loading"
ref="instance" ref="instance"
:error="error || value?.reachable == false" :error="error || value?.reachable == false"
@ -25,7 +25,7 @@
</div> </div>
<div class="children fade-in" v-if="hasChildren && !isCollapsed"> <div class="children fade-in" v-if="hasChildren && !isCollapsed">
<div class="child" v-for="entity in computedChildren" :key="entity.id"> <div class="child" v-for="entity in children" :key="entity.id">
<Entity <Entity
:value="entity" :value="entity"
:parent="value" :parent="value"
@ -57,19 +57,12 @@ export default {
}, },
computed: { computed: {
computedChildren() {
return Object.values(this.children || {}).filter((child) => child)
},
hasChildren() { hasChildren() {
return !!this.computedChildren.length return !!Object.keys(this.children).length
}, },
isCollapsed() { isCollapsed() {
if (!this.hasChildren) return !this.hasChildren ? true : this.collapsed
return true
return this.collapsed
}, },
instance() { instance() {
@ -90,16 +83,16 @@ export default {
}, },
childrenByParentId(parentId) { childrenByParentId(parentId) {
return Object.values(this.allEntities || {}). const parentEntity = this.allEntities?.[parentId]
filter( if (!parentEntity)
(entity) => entity return {}
&& entity.parent_id === parentId
&& !entity.is_configuration return (parentEntity.children_ids || []).reduce((obj, entityId) => {
). const entity = this.allEntities[entityId]
reduce((obj, entity) => { if (entity && !entity.is_configuration)
obj[entity.id] = entity obj[entity.id] = entity
return obj return obj
}, {}) }, {})
}, },
onClick(event) { onClick(event) {
@ -131,7 +124,7 @@ export default {
if (!isChildUpdate) if (!isChildUpdate)
return return
this.setJustUpdated() this.notifyUpdate()
}, },
toggleCollapsed() { toggleCollapsed() {
@ -141,7 +134,7 @@ export default {
this.instance.collapsed = !this.instance.collapsed this.instance.collapsed = !this.instance.collapsed
}, },
setJustUpdated() { notifyUpdate() {
this.justUpdated = true this.justUpdated = true
const self = this; const self = this;
setTimeout(() => self.justUpdated = false, 1000) setTimeout(() => self.justUpdated = false, 1000)
@ -160,7 +153,7 @@ export default {
if (this.valuesEqual(oldValue, newValue)) if (this.valuesEqual(oldValue, newValue))
return false return false
this.setJustUpdated() this.notifyUpdate()
this.$emit('update', {value: newValue}) this.$emit('update', {value: newValue})
} }
) )

View File

@ -55,8 +55,8 @@
<div class="body"> <div class="body">
<div class="entity-frame" <div class="entity-frame"
v-for="entity in group.entities" v-for="entity in Object.values(group.entities).sort((a, b) => a.name.localeCompare(b.name))"
:key="entity.id"> :key="entity.id">
<Entity <Entity
:value="entity" :value="entity"
:children="childrenByParentId(entity.id)" :children="childrenByParentId(entity.id)"
@ -64,6 +64,7 @@
@show-modal="onEntityModal($event)" @show-modal="onEntityModal($event)"
@input="onEntityInput(entity)" @input="onEntityInput(entity)"
:error="!!errorEntities[entity.id]" :error="!!errorEntities[entity.id]"
:key="entity.id"
:loading="!!loadingEntities[entity.id]" :loading="!!loadingEntities[entity.id]"
@loading="loadingEntities[entity.id] = $event" @loading="loadingEntities[entity.id] = $event"
v-if="!entity.parent_id" v-if="!entity.parent_id"
@ -122,6 +123,12 @@ export default {
errorEntities: {}, errorEntities: {},
entityTimeouts: {}, entityTimeouts: {},
entities: {}, entities: {},
entityGroups: {
id: {},
category: {},
plugin: {},
type: {},
},
modalEntityId: null, modalEntityId: null,
modalVisible: false, modalVisible: false,
variableModalVisible: false, variableModalVisible: false,
@ -141,10 +148,6 @@ export default {
return icons return icons
}, },
entityTypes() {
return this.groupEntities('type')
},
typesByCategory() { typesByCategory() {
return Object.entries(meta).reduce((obj, [type, meta]) => { return Object.entries(meta).reduce((obj, [type, meta]) => {
obj[meta.name_plural] = type obj[meta.name_plural] = type
@ -152,21 +155,10 @@ export default {
}, {}) }, {})
}, },
entityGroups() {
return {
'id': Object.entries(this.groupEntities('id')).reduce((obj, [id, entities]) => {
obj[id] = entities[0]
return obj
}, {}),
'category': this.groupEntities('category'),
'plugin': this.groupEntities('plugin'),
}
},
displayGroups() { displayGroups() {
return Object.entries(this.entityGroups[this.selector.grouping]). return Object.entries(this.entityGroups[this.selector.grouping]).
filter( filter(
(entry) => entry[1].filter( (entry) => Object.values(entry[1]).filter(
(e) => (e) =>
!!this.selector.selectedEntities[e.id] && e.parent_id == null !!this.selector.selectedEntities[e.id] && e.parent_id == null
).length > 0 ).length > 0
@ -175,7 +167,7 @@ export default {
([grouping, entities]) => { ([grouping, entities]) => {
return { return {
name: grouping, name: grouping,
entities: entities.filter( entities: Object.values(entities).filter(
(e) => e.id in this.selector.selectedEntities (e) => e.id in this.selector.selectedEntities
), ),
} }
@ -186,19 +178,32 @@ export default {
}, },
methods: { methods: {
groupEntities(attr) { addEntity(entity) {
return Object.values(this.entities). if (entity.parent_id != null)
filter((entity) => entity.parent_id == null). return // Only group entities that have no parent
reduce((obj, entity) => {
const entities = obj[entity[attr]] || {}
entities[entity.id] = entity
obj[entity[attr]] = Object.values(entities).sort((a, b) => { this.entities[entity.id] = entity;
return a.name.localeCompare(b.name) ['id', 'type', 'category', 'plugin'].forEach((attr) => {
}) if (entity[attr] == null)
return
return obj if (!this.entityGroups[attr][entity[attr]])
}, {}) this.entityGroups[attr][entity[attr]] = {}
this.entityGroups[attr][entity[attr]][entity.id] = entity
})
},
removeEntity(entity) {
if (entity.parent_id != null)
return // Only group entities that have no parent
['id', 'type', 'category', 'plugin'].forEach((attr) => {
if (this.entityGroups[attr][entity[attr]][entity.id])
delete this.entityGroups[attr][entity[attr]][entity.id]
})
if (this.entities[entity.id])
delete this.entities[entity.id]
}, },
_shouldSkipLoading(entity) { _shouldSkipLoading(entity) {
@ -236,6 +241,7 @@ export default {
if (this.entityTimeouts[id]) if (this.entityTimeouts[id])
clearTimeout(this.entityTimeouts[id]) clearTimeout(this.entityTimeouts[id])
this.addEntity(entity)
this.entityTimeouts[id] = setTimeout(() => { this.entityTimeouts[id] = setTimeout(() => {
if (self.loadingEntities[id]) if (self.loadingEntities[id])
delete self.loadingEntities[id] delete self.loadingEntities[id]
@ -266,6 +272,7 @@ export default {
} }
obj[entity.id] = entity obj[entity.id] = entity
this.addEntity(entity)
return obj return obj
}, {}) }, {})
@ -275,30 +282,26 @@ export default {
} }
}, },
childrenByParentId(parentId) { childrenByParentId(parentId, selectConfig) {
return Object.values(this.entities). const entity = this.entities?.[parentId]
filter( if (!entity?.children_ids?.length)
(entity) => entity return {}
&& entity.parent_id === parentId
&& !entity.is_configuration return entity.children_ids.reduce((obj, id) => {
). const child = this.entities[id]
reduce((obj, entity) => { if (
obj[entity.id] = entity child && (
return obj (!selectConfig && !child.is_configuration) ||
}, {}) (selectConfig && child.is_configuration)
)
)
obj[id] = this.entities[id]
return obj
}, {})
}, },
configValuesByParentId(parentId) { configValuesByParentId(parentId) {
return Object.values(this.entities). return this.childrenByParentId(parentId, true)
filter(
(entity) => entity
&& entity.parent_id === parentId
&& entity.is_configuration
).
reduce((obj, entity) => {
obj[entity.id] = entity
return obj
}, {})
}, },
clearEntityTimeouts(entityId) { clearEntityTimeouts(entityId) {
@ -343,7 +346,7 @@ export default {
...(event.entity?.meta || {}), ...(event.entity?.meta || {}),
} }
this.entities[entityId] = entity this.addEntity(entity)
bus.publishEntity(entity) bus.publishEntity(entity)
}, },
@ -354,7 +357,7 @@ export default {
if (entityId === this.modalEntityId) if (entityId === this.modalEntityId)
this.modalEntityId = null this.modalEntityId = null
if (this.entities[entityId]) if (this.entities[entityId])
delete this.entities[entityId] this.removeEntity(this.entities[entityId])
}, },
onEntityModal(entityId) { onEntityModal(entityId) {