Skip to content

Commit

Permalink
fear(NcSettingsSelectGroup): Reduce API calls for loading groups
Browse files Browse the repository at this point in the history
* Debounce search requests when typing
* Cache initial groups (empty search) for all components of current page

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
  • Loading branch information
susnux committed May 31, 2023
1 parent dacbabc commit 46c7eef
Showing 1 changed file with 82 additions and 15 deletions.
97 changes: 82 additions & 15 deletions src/components/NcSettingsSelectGroup/NcSettingsSelectGroup.vue
Expand Up @@ -27,15 +27,16 @@
<template>
<section>
<NcSettingsSelectGroup v-model="groups" placeholder="Select user groups" label="The hidden label" />
<NcSettingsSelectGroup v-model="groups" :disabled="true" label="Also a fallback for the placeholder" />
<div>You have selected: <code>{{ groups }}</code></div>
<NcSettingsSelectGroup v-model="otherGroups" :disabled="true" label="Also a fallback for the placeholder" />
<div>You have selected: <code>{{ groups }}</code> and <code>{{ otherGroups }}</code></div>
</section>
</template>
<script>
export default {
data() {
return {
groups: []
groups: [],
otherGroups: ['admin']
}
}
}
Expand All @@ -61,7 +62,7 @@ section * {
:close-on-select="false"
:disabled="disabled"
@input="update"
@search="findGroup" />
@search="onSearch" />
</div>
</template>

Expand All @@ -74,6 +75,7 @@ import GenRandomId from '../../utils/GenRandomId.js'
import axios from '@nextcloud/axios'
import { showError } from '@nextcloud/dialogs'
import { generateOcsUrl } from '@nextcloud/router'
import { debounce } from 'debounce'
export default {
name: 'NcSettingsSelectGroup',
Expand Down Expand Up @@ -131,16 +133,26 @@ export default {
],
data() {
return {
/** Temporary store to cache groups */
groups: {},
randId: GenRandomId(),
}
},
computed: {
inputValue() {
return this.getValueObject
/**
* Validate input value and only return valid strings (group IDs)
*
* @return {string[]}
*/
filteredValue() {
return this.value.filter((group) => group !== '' && typeof group === 'string')
},
getValueObject() {
return this.value.filter((group) => group !== '' && typeof group !== 'undefined').map(
/**
* value property converted to an array of group objects used as input for the NcSelect
*/
inputValue() {
return this.filteredValue.map(
(id) => {
if (typeof this.groups[id] === 'undefined') {
return {
Expand All @@ -152,27 +164,75 @@ export default {
}
)
},
/**
* Convert groups object to array of groups required for NcSelect.options
* Filter out currently selected values
*
* @return {object[]}
*/
groupsArray() {
return Object.values(this.groups)
return Object.values(this.groups).filter(g => !this.value.includes(g.id))
},
},
watch: {
/**
* If the value is changed, check that all groups are loaded so we show the correct display name
*/
value: {
handler() {
const loadedGroupIds = Object.keys(this.groups)
const missing = this.filteredValue.filter(group => !loadedGroupIds.includes(group))
missing.forEach((groupId) => {
this.loadGroup(groupId)
})
},
// Run the watch handler also when the component is initially mounted
immediate: true,
},
},
/**
* Load groups matching the empty query to reduce API calls
*/
async mounted() {
// version scoped to prevent issues with different library versions
const storageName = `${appName}:${appVersion}/initialGroups`
let savedGroups = window.sessionStorage.getItem(storageName)
if (savedGroups) {
savedGroups = Object.fromEntries(JSON.parse(savedGroups).map(group => [group.id, group]))
this.groups = { ...this.groups, ...savedGroups }
} else {
await this.loadGroup('')
window.sessionStorage.setItem(storageName, JSON.stringify(Object.values(this.groups)))
}
},
methods: {
/**
* Called when a new group is selected or previous group is deselected to emit the update event
*
* @param {object[]} updatedValue Array of selected groups
*/
update(updatedValue) {
const value = updatedValue.map((element) => element.id)
/** Emitted when the groups selection changes<br />**Payload:** `value` (`Array`) - *Ids of selected groups */
this.$emit('input', value)
},
async findGroup(query) {
/**
* Use provisioning API to search for given group and save it in the groups object
*
* @param {string} query The query like parts of the id oder display name
* @return {boolean}
*/
async loadGroup(query) {
try {
query = typeof query === 'string' ? encodeURI(query) : ''
const response = await axios.get(generateOcsUrl(`cloud/groups/details?search=${query}&limit=10`, 2))
if (Object.keys(response.data.ocs.data.groups).length > 0) {
response.data.ocs.data.groups.forEach((element) => {
if (typeof this.groups[element.id] === 'undefined') {
this.$set(this.groups, element.id, element)
}
})
const newGroups = Object.fromEntries(response.data.ocs.data.groups.map((element) => [element.id, element]))
this.groups = { ...this.groups, ...newGroups }
return true
}
} catch (error) {
Expand All @@ -182,6 +242,13 @@ export default {
}
return false
},
/**
* Debounce the group search (reduce API calls)
*/
onSearch: debounce(function(query) {
this.loadGroup(query)
}, 200),
},
}
</script>

0 comments on commit 46c7eef

Please sign in to comment.