Improve record selector resolution in blocks
This commit is contained in:
@@ -215,6 +215,7 @@ export default {
|
||||
...mapGetters({
|
||||
getModuleByID: 'module/getByID',
|
||||
findUserByID: 'user/findByID',
|
||||
findRecordsByIDs: 'record/findByIDs',
|
||||
}),
|
||||
|
||||
options () {
|
||||
@@ -292,7 +293,9 @@ export default {
|
||||
methods: {
|
||||
...mapActions({
|
||||
findModuleByID: 'module/findByID',
|
||||
resolveUsers: 'user/fetchUsers',
|
||||
resolveUsers: 'user/resolveUsers',
|
||||
resolveRecords: 'record/resolveRecords',
|
||||
updateRecords: 'record/updateRecords',
|
||||
}),
|
||||
|
||||
getRecord (index = undefined) {
|
||||
@@ -403,6 +406,8 @@ export default {
|
||||
this.filter.nextPage = filter.nextPage
|
||||
this.filter.prevPage = filter.prevPage
|
||||
|
||||
this.updateRecords(set)
|
||||
|
||||
return this.formatRecordValues(set.map(({ recordID }) => recordID)).then(() => {
|
||||
this.records = set.map(r => new compose.Record(this.module, r))
|
||||
})
|
||||
@@ -416,67 +421,67 @@ export default {
|
||||
return [this.field.options.labelField].filter(f => !!f).join(', ')
|
||||
},
|
||||
|
||||
async formatRecordValues (value) {
|
||||
value = Array.isArray(value) ? value : [value].filter(v => v) || []
|
||||
async formatRecordValues (recordIDs) {
|
||||
recordIDs = Array.isArray(recordIDs) ? recordIDs : [recordIDs].filter(v => v) || []
|
||||
const { namespaceID = NoID } = this.namespace
|
||||
const { moduleID = NoID, labelField, recordLabelField } = this.field.options
|
||||
|
||||
if (!value.length || [moduleID, namespaceID].includes(NoID) || !labelField) {
|
||||
if (!recordIDs.length || [moduleID, namespaceID].includes(NoID) || !labelField) {
|
||||
return
|
||||
}
|
||||
|
||||
this.processing = true
|
||||
return this.findModuleByID({ namespace: this.namespace, moduleID }).then(async module => {
|
||||
const relatedField = module.fields.find(({ name }) => name === labelField)
|
||||
const records = this.findRecordsByIDs(recordIDs).map(r => new compose.Record(module, r))
|
||||
const mappedIDs = {}
|
||||
|
||||
// Get configured module/field
|
||||
return this.findModuleByID({ namespace: this.namespace, moduleID }).then(module => {
|
||||
let relatedField = module.fields.find(({ name }) => name === labelField)
|
||||
const query = value.map(recordID => `recordID = ${recordID}`).join(' OR ')
|
||||
if (relatedField.kind === 'Record' && recordLabelField) {
|
||||
this.processing = true
|
||||
|
||||
return this.$ComposeAPI.recordList({ namespaceID, moduleID, query, deleted: 1 }).then(async ({ set = [] }) => {
|
||||
if (recordLabelField) {
|
||||
set = await this.findModuleByID({ namespaceID, moduleID: relatedField.options.moduleID }).then(relatedModule => {
|
||||
const mappedIDs = {}
|
||||
const queryIDs = []
|
||||
const relatedModule = await this.findModuleByID({ namespaceID, moduleID: relatedField.options.moduleID })
|
||||
const relatedRecordIDs = new Set()
|
||||
|
||||
set.forEach(r => {
|
||||
r = new compose.Record(module, r)
|
||||
mappedIDs[r.values[labelField]] = r.recordID
|
||||
queryIDs.push(`recordID = ${r.values[labelField]}`)
|
||||
})
|
||||
records.forEach(r => {
|
||||
const recordValue = relatedField.isMulti ? r.values[relatedField.name] : [r.values[relatedField.name]]
|
||||
recordValue.forEach(rID => relatedRecordIDs.add(rID))
|
||||
})
|
||||
await this.resolveRecords({ namespaceID, moduleID: relatedModule.moduleID, recordIDs: [...relatedRecordIDs] })
|
||||
|
||||
return this.$ComposeAPI.recordList({ namespaceID, moduleID: relatedField.options.moduleID, query: queryIDs.join(' OR '), deleted: 1 }).then(({ set: resolvedSet = [] }) => {
|
||||
relatedField = relatedModule.fields.find(({ name }) => name === this.field.options.recordLabelField)
|
||||
resolvedSet.forEach(r => {
|
||||
mappedIDs[r.recordID] = r
|
||||
})
|
||||
const relatedLabelField = relatedModule.fields.find(({ name }) => name === recordLabelField)
|
||||
|
||||
return set.map(r => {
|
||||
r = new compose.Record(module, r)
|
||||
const relatedRecord = mappedIDs[r.values[labelField]]
|
||||
relatedRecord.recordID = r.recordID
|
||||
return new compose.Record(relatedModule, relatedRecord)
|
||||
})
|
||||
})
|
||||
})
|
||||
} else {
|
||||
set = set.map(r => new compose.Record(module, r))
|
||||
}
|
||||
for (let r of await this.findRecordsByIDs([...relatedRecordIDs])) {
|
||||
r = new compose.Record(relatedModule, r)
|
||||
let relatedRecordValue = relatedLabelField.isMulti ? r.values[relatedLabelField.name] : [r.values[relatedLabelField.name]]
|
||||
|
||||
for (const record of set) {
|
||||
let recordValue = relatedField.isMulti ? record.values[relatedField.name] : [record.values[relatedField.name]]
|
||||
|
||||
if (recordValue.length && relatedField.kind === 'User') {
|
||||
recordValue = await Promise.all(recordValue.map(async v => {
|
||||
if (!this.findUserByID(v)) {
|
||||
await this.resolveUsers(v)
|
||||
}
|
||||
|
||||
return relatedField.formatter(this.findUserByID(v))
|
||||
}))
|
||||
if (relatedLabelField.kind === 'User') {
|
||||
await this.resolveUsers(relatedRecordValue)
|
||||
relatedRecordValue = relatedRecordValue.map(v => relatedLabelField.formatter(this.findUserByID(v)))
|
||||
}
|
||||
|
||||
this.$set(this.recordValues, record.recordID, recordValue.join(relatedField.options.multiDelimiter))
|
||||
mappedIDs[r.recordID] = relatedRecordValue.join(relatedLabelField.options.multiDelimiter)
|
||||
}
|
||||
} else if (relatedField.kind === 'User') {
|
||||
this.processing = true
|
||||
|
||||
const relatedUserIDs = new Set()
|
||||
records.forEach(r => {
|
||||
const recordValue = relatedField.isMulti ? r.values[relatedField.name] : [r.values[relatedField.name]]
|
||||
recordValue.forEach(uID => relatedUserIDs.add(uID))
|
||||
})
|
||||
|
||||
await this.resolveUsers([...relatedUserIDs])
|
||||
}
|
||||
|
||||
records.forEach(record => {
|
||||
let recordValue = relatedField.isMulti ? record.values[relatedField.name] : [record.values[relatedField.name]]
|
||||
|
||||
if (relatedField.kind === 'User') {
|
||||
recordValue = recordValue.map(v => relatedField.formatter(this.findUserByID(v)))
|
||||
} else if (relatedField.kind === 'Record' && recordLabelField) {
|
||||
recordValue = recordValue.map(v => mappedIDs[v])
|
||||
}
|
||||
|
||||
this.$set(this.recordValues, record.recordID, recordValue.join(relatedField.options.multiDelimiter))
|
||||
})
|
||||
}).finally(() => {
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -55,6 +55,7 @@ export default {
|
||||
...mapGetters({
|
||||
pages: 'page/set',
|
||||
findUserByID: 'user/findByID',
|
||||
findRecordsByIDs: 'record/findByIDs',
|
||||
}),
|
||||
|
||||
formattedValue () {
|
||||
@@ -84,7 +85,8 @@ export default {
|
||||
methods: {
|
||||
...mapActions({
|
||||
findModuleByID: 'module/findByID',
|
||||
resolveUsers: 'user/fetchUsers',
|
||||
resolveUsers: 'user/resolveUsers',
|
||||
resolveRecords: 'record/resolveRecords',
|
||||
}),
|
||||
|
||||
linkToRecord (recordID) {
|
||||
@@ -101,61 +103,67 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
async formatRecordValues (value) {
|
||||
value = Array.isArray(value) ? value : [value].filter(v => v) || []
|
||||
async formatRecordValues (recordIDs) {
|
||||
recordIDs = Array.isArray(recordIDs) ? recordIDs : [recordIDs].filter(v => v) || []
|
||||
const { namespaceID = NoID } = this.namespace
|
||||
const { moduleID = NoID, labelField, recordLabelField } = this.field.options
|
||||
|
||||
if (!value.length || [moduleID, namespaceID].includes(NoID) || !labelField) {
|
||||
if (!recordIDs.length || [moduleID, namespaceID].includes(NoID) || !labelField) {
|
||||
return
|
||||
}
|
||||
|
||||
this.processing = true
|
||||
return this.findModuleByID({ namespace: this.namespace, moduleID }).then(async module => {
|
||||
const relatedField = module.fields.find(({ name }) => name === labelField)
|
||||
const records = this.findRecordsByIDs(recordIDs).map(r => new compose.Record(module, r))
|
||||
const mappedIDs = {}
|
||||
|
||||
// Get configured module/field
|
||||
return this.findModuleByID({ namespace: this.namespace, moduleID }).then(module => {
|
||||
let relatedField = module.fields.find(({ name }) => name === labelField)
|
||||
const query = value.map(recordID => `recordID = ${recordID}`).join(' OR ')
|
||||
if (relatedField.kind === 'Record' && recordLabelField) {
|
||||
this.processing = true
|
||||
|
||||
return this.$ComposeAPI.recordList({ namespaceID, moduleID, query, deleted: 1 }).then(async ({ set = [] }) => {
|
||||
if (recordLabelField) {
|
||||
set = await this.findModuleByID({ namespaceID, moduleID: relatedField.options.moduleID }).then(relatedModule => {
|
||||
const mappedIDs = {}
|
||||
const queryIDs = []
|
||||
const relatedModule = await this.findModuleByID({ namespaceID, moduleID: relatedField.options.moduleID })
|
||||
const relatedRecordIDs = new Set()
|
||||
|
||||
set.forEach(r => {
|
||||
r = new compose.Record(module, r)
|
||||
mappedIDs[r.values[labelField]] = r.recordID
|
||||
queryIDs.push(`recordID = ${r.values[labelField]}`)
|
||||
})
|
||||
records.forEach(r => {
|
||||
const recordValue = relatedField.isMulti ? r.values[relatedField.name] : [r.values[relatedField.name]]
|
||||
recordValue.forEach(rID => relatedRecordIDs.add(rID))
|
||||
})
|
||||
await this.resolveRecords({ namespaceID, moduleID: relatedModule.moduleID, recordIDs: [...relatedRecordIDs] })
|
||||
|
||||
return this.$ComposeAPI.recordList({ namespaceID, moduleID: relatedField.options.moduleID, query: queryIDs.join(' OR '), deleted: 1 }).then(({ set = [] }) => {
|
||||
relatedField = relatedModule.fields.find(({ name }) => name === this.field.options.recordLabelField)
|
||||
return set.map(r => {
|
||||
r.recordID = mappedIDs[r.recordID]
|
||||
return new compose.Record(relatedModule, r)
|
||||
})
|
||||
})
|
||||
})
|
||||
} else {
|
||||
set = set.map(r => new compose.Record(module, r))
|
||||
}
|
||||
const relatedLabelField = relatedModule.fields.find(({ name }) => name === recordLabelField)
|
||||
|
||||
for (const record of set) {
|
||||
let recordValue = relatedField.isMulti ? record.values[relatedField.name] : [record.values[relatedField.name]]
|
||||
for (let r of await this.findRecordsByIDs([...relatedRecordIDs])) {
|
||||
r = new compose.Record(relatedModule, r)
|
||||
let relatedRecordValue = relatedLabelField.isMulti ? r.values[relatedLabelField.name] : [r.values[relatedLabelField.name]]
|
||||
|
||||
if (recordValue.length && relatedField.kind === 'User') {
|
||||
recordValue = await Promise.all(recordValue.map(async v => {
|
||||
if (!this.findUserByID(v)) {
|
||||
await this.resolveUsers(v)
|
||||
}
|
||||
|
||||
return relatedField.formatter(this.findUserByID(v))
|
||||
}))
|
||||
if (relatedLabelField.kind === 'User') {
|
||||
await this.resolveUsers(relatedRecordValue)
|
||||
relatedRecordValue = relatedRecordValue.map(v => relatedLabelField.formatter(this.findUserByID(v)))
|
||||
}
|
||||
|
||||
this.$set(this.recordValues, record.recordID, recordValue.join(relatedField.options.multiDelimiter))
|
||||
mappedIDs[r.recordID] = relatedRecordValue.join(relatedLabelField.options.multiDelimiter)
|
||||
}
|
||||
} else if (relatedField.kind === 'User') {
|
||||
this.processing = true
|
||||
|
||||
const relatedUserIDs = new Set()
|
||||
records.forEach(r => {
|
||||
const recordValue = relatedField.isMulti ? r.values[relatedField.name] : [r.values[relatedField.name]]
|
||||
recordValue.forEach(uID => relatedUserIDs.add(uID))
|
||||
})
|
||||
|
||||
await this.resolveUsers([...relatedUserIDs])
|
||||
}
|
||||
|
||||
records.forEach(record => {
|
||||
let recordValue = relatedField.isMulti ? record.values[relatedField.name] : [record.values[relatedField.name]]
|
||||
|
||||
if (relatedField.kind === 'User') {
|
||||
recordValue = recordValue.map(v => relatedField.formatter(this.findUserByID(v)))
|
||||
} else if (relatedField.kind === 'Record' && recordLabelField) {
|
||||
recordValue = recordValue.map(v => mappedIDs[v])
|
||||
}
|
||||
|
||||
this.$set(this.recordValues, record.recordID, recordValue.join(relatedField.options.multiDelimiter))
|
||||
})
|
||||
}).finally(() => {
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -67,6 +67,7 @@ import base from './base'
|
||||
import FieldViewer from 'corteza-webapp-compose/src/components/ModuleFields/Viewer'
|
||||
import Hint from 'corteza-webapp-compose/src/components/Common/Hint.vue'
|
||||
import users from 'corteza-webapp-compose/src/mixins/users'
|
||||
import records from 'corteza-webapp-compose/src/mixins/records'
|
||||
import conditionalFields from 'corteza-webapp-compose/src/mixins/conditionalFields'
|
||||
|
||||
export default {
|
||||
@@ -83,6 +84,7 @@ export default {
|
||||
|
||||
mixins: [
|
||||
users,
|
||||
records,
|
||||
conditionalFields,
|
||||
],
|
||||
|
||||
@@ -131,17 +133,24 @@ export default {
|
||||
handler (recordID) {
|
||||
if (!recordID) return
|
||||
|
||||
this.evaluating = true
|
||||
|
||||
this.evaluateExpressions()
|
||||
.finally(() => {
|
||||
this.evaluating = false
|
||||
})
|
||||
let resolutions = []
|
||||
|
||||
if (recordID !== NoID) {
|
||||
this.fetchUsers(this.fields, [this.record])
|
||||
resolutions = [
|
||||
this.fetchUsers(this.fields, [this.record]),
|
||||
this.fetchRecords(this.namespace.namespaceID, this.fields, [this.record]),
|
||||
]
|
||||
}
|
||||
|
||||
this.evaluating = true
|
||||
|
||||
Promise.all([
|
||||
...resolutions,
|
||||
this.evaluateExpressions(),
|
||||
]).finally(() => {
|
||||
this.evaluating = false
|
||||
})
|
||||
|
||||
if (this.options.referenceModuleID) {
|
||||
this.fetchReferenceModule(this.options.referenceModuleID)
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
import { validator, NoID } from '@cortezaproject/corteza-js'
|
||||
import base from './base'
|
||||
import users from 'corteza-webapp-compose/src/mixins/users'
|
||||
import records from 'corteza-webapp-compose/src/mixins/records'
|
||||
import FieldEditor from 'corteza-webapp-compose/src/components/ModuleFields/Editor'
|
||||
import FieldViewer from 'corteza-webapp-compose/src/components/ModuleFields/Viewer'
|
||||
import Hint from 'corteza-webapp-compose/src/components/Common/Hint.vue'
|
||||
@@ -99,6 +100,7 @@ export default {
|
||||
|
||||
mixins: [
|
||||
users,
|
||||
records,
|
||||
conditionalFields,
|
||||
],
|
||||
|
||||
@@ -145,16 +147,23 @@ export default {
|
||||
handler (recordID) {
|
||||
if (!recordID) return
|
||||
|
||||
this.evaluating = true
|
||||
|
||||
this.evaluateExpressions()
|
||||
.finally(() => {
|
||||
this.evaluating = false
|
||||
})
|
||||
let resolutions = []
|
||||
|
||||
if (recordID !== NoID) {
|
||||
this.fetchUsers(this.fields, [this.record])
|
||||
resolutions = [
|
||||
this.fetchUsers(this.fields, [this.record]),
|
||||
this.fetchRecords(this.namespace.namespaceID, this.fields, [this.record]),
|
||||
]
|
||||
}
|
||||
|
||||
this.evaluating = true
|
||||
|
||||
Promise.all([
|
||||
...resolutions,
|
||||
this.evaluateExpressions(),
|
||||
]).finally(() => {
|
||||
this.evaluating = false
|
||||
})
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -627,7 +627,7 @@
|
||||
<div class="text-truncate">
|
||||
<div
|
||||
v-if="options.showTotalCount"
|
||||
class="ml-2 text-nowrap"
|
||||
class="ml-2 text-nowrap my-1"
|
||||
>
|
||||
<span
|
||||
v-if="pagination.count > options.perPage"
|
||||
@@ -732,6 +732,7 @@ import ImporterModal from 'corteza-webapp-compose/src/components/Public/Record/I
|
||||
import AutomationButtons from './Shared/AutomationButtons'
|
||||
import { compose, validator, NoID } from '@cortezaproject/corteza-js'
|
||||
import users from 'corteza-webapp-compose/src/mixins/users'
|
||||
import records from 'corteza-webapp-compose/src/mixins/records'
|
||||
import { evaluatePrefilter, queryToFilter } from 'corteza-webapp-compose/src/lib/record-filter'
|
||||
import { getItem, setItem, removeItem } from 'corteza-webapp-compose/src/lib/local-storage'
|
||||
import { components, url } from '@cortezaproject/corteza-vue'
|
||||
@@ -764,6 +765,7 @@ export default {
|
||||
|
||||
mixins: [
|
||||
users,
|
||||
records,
|
||||
],
|
||||
|
||||
props: {
|
||||
@@ -1576,9 +1578,13 @@ export default {
|
||||
|
||||
// Extract user IDs from record values and load all users
|
||||
const fields = this.fields.filter(f => f.moduleField).map(f => f.moduleField)
|
||||
this.fetchUsers(fields, records)
|
||||
|
||||
this.items = records.map(r => this.wrapRecord(r))
|
||||
return Promise.all([
|
||||
this.fetchUsers(fields, records),
|
||||
this.fetchRecords(namespaceID, fields, records),
|
||||
]).then(() => {
|
||||
this.items = records.map(r => this.wrapRecord(r))
|
||||
})
|
||||
})
|
||||
.catch(this.toastErrorHandler(this.$t('notification:record.listLoadFailed')))
|
||||
.finally(() => {
|
||||
|
||||
@@ -36,7 +36,6 @@
|
||||
md="6"
|
||||
>
|
||||
<b-form-group
|
||||
v-if="recordListModule"
|
||||
:label="$t('recordList.record.inlineEditorAllow')"
|
||||
label-class="text-primary"
|
||||
>
|
||||
|
||||
@@ -35,55 +35,50 @@
|
||||
{{ $t('recordOrganizer.noRecords') }}
|
||||
</div>
|
||||
</template>
|
||||
<router-link
|
||||
|
||||
<b-card
|
||||
v-for="record in records"
|
||||
:key="`${record.recordID}`"
|
||||
tag="div"
|
||||
class="record-item mb-2"
|
||||
:class="{ 'pointer': roRecordPage }"
|
||||
:to="{ name: 'page.record', params: { pageID: (roRecordPage || {}).pageID, recordID: record.recordID }, query: null }"
|
||||
body-class="px-2 py-1"
|
||||
class="record-item border border-light rounded mb-2 grab"
|
||||
@click="handleRecordClick(record)"
|
||||
>
|
||||
<b-card
|
||||
body-class="px-2 py-1"
|
||||
class="border rounded"
|
||||
<div
|
||||
v-if="labelField"
|
||||
class="d-flex mb-1"
|
||||
>
|
||||
<b-card-title
|
||||
v-if="labelField"
|
||||
title-tag="div"
|
||||
class="mb-1"
|
||||
<field-viewer
|
||||
v-if="labelField.canReadRecordValue"
|
||||
:field="labelField"
|
||||
:record="record"
|
||||
:namespace="namespace"
|
||||
value-only
|
||||
/>
|
||||
<i
|
||||
v-else
|
||||
class="text-secondary"
|
||||
>{{ $t('field.noPermission') }}</i>
|
||||
</div>
|
||||
|
||||
<b-card-text
|
||||
v-if="descriptionField"
|
||||
class="d-flex small"
|
||||
>
|
||||
<field-viewer
|
||||
v-if="descriptionField.canReadRecordValue"
|
||||
:field="descriptionField"
|
||||
:record="record"
|
||||
:namespace="namespace"
|
||||
value-only
|
||||
/>
|
||||
<i
|
||||
v-else
|
||||
class="text-secondary"
|
||||
>
|
||||
<field-viewer
|
||||
v-if="labelField.canReadRecordValue"
|
||||
:field="labelField"
|
||||
:record="record"
|
||||
:namespace="namespace"
|
||||
value-only
|
||||
/>
|
||||
<i
|
||||
v-else
|
||||
class="text-secondary"
|
||||
>{{ $t('field.noPermission') }}</i>
|
||||
</b-card-title>
|
||||
<b-card-text
|
||||
v-if="descriptionField"
|
||||
class="small"
|
||||
>
|
||||
<field-viewer
|
||||
v-if="descriptionField.canReadRecordValue"
|
||||
:field="descriptionField"
|
||||
:record="record"
|
||||
:namespace="namespace"
|
||||
value-only
|
||||
/>
|
||||
<i
|
||||
v-else
|
||||
class="text-secondary"
|
||||
>
|
||||
{{ $t('field.noPermission') }}
|
||||
</i>
|
||||
</b-card-text>
|
||||
</b-card>
|
||||
</router-link>
|
||||
{{ $t('field.noPermission') }}
|
||||
</i>
|
||||
</b-card-text>
|
||||
</b-card>
|
||||
</draggable>
|
||||
<div
|
||||
v-else
|
||||
@@ -118,6 +113,7 @@ import base from './base'
|
||||
import draggable from 'vuedraggable'
|
||||
import FieldViewer from 'corteza-webapp-compose/src/components/ModuleFields/Viewer'
|
||||
import users from 'corteza-webapp-compose/src/mixins/users'
|
||||
import records from 'corteza-webapp-compose/src/mixins/records'
|
||||
import { evaluatePrefilter, getFieldFilter } from 'corteza-webapp-compose/src/lib/record-filter'
|
||||
import { compose, NoID } from '@cortezaproject/corteza-js'
|
||||
|
||||
@@ -135,6 +131,7 @@ export default {
|
||||
|
||||
mixins: [
|
||||
users,
|
||||
records,
|
||||
],
|
||||
|
||||
data () {
|
||||
@@ -412,7 +409,7 @@ export default {
|
||||
args: Object.keys(args).map(name => ({ name, value: String(args[name]) })),
|
||||
}
|
||||
|
||||
return this.$ComposeAPI.recordExec(params).then(this.fetchRecords)
|
||||
return this.$ComposeAPI.recordExec(params).then(this.pullRecords)
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -422,7 +419,7 @@ export default {
|
||||
* @param {String} query Filter records
|
||||
* @returns {Promise<Record[]>}
|
||||
*/
|
||||
async fetchRecords () {
|
||||
async pullRecords () {
|
||||
if (!this.roModule) {
|
||||
return
|
||||
}
|
||||
@@ -432,17 +429,21 @@ export default {
|
||||
}
|
||||
|
||||
const query = this.expandFilter()
|
||||
const { labelField, descriptionField, positionField } = this.options
|
||||
const { positionField } = this.options
|
||||
const { moduleID, namespaceID } = this.roModule
|
||||
const sort = positionField || `updatedAt, ${labelField || descriptionField}`
|
||||
const sort = positionField || 'updatedAt'
|
||||
|
||||
this.processing = true
|
||||
|
||||
return this.$ComposeAPI.recordList({ namespaceID, moduleID, query, sort })
|
||||
.then(({ set }) => {
|
||||
this.records = set.map(r => Object.freeze(new compose.Record(this.roModule, r)))
|
||||
const fields = [this.labelField, this.descriptionField].filter(f => !!f)
|
||||
this.fetchUsers(fields, this.records)
|
||||
this.records = set.map(r => Object.freeze(new compose.Record(this.roModule, r)))
|
||||
|
||||
return Promise.all([
|
||||
this.fetchUsers(fields, this.records),
|
||||
this.fetchRecords(namespaceID, fields, this.records),
|
||||
])
|
||||
}).catch(e => {
|
||||
console.error(e)
|
||||
}).finally(() => {
|
||||
@@ -450,8 +451,23 @@ export default {
|
||||
})
|
||||
},
|
||||
|
||||
handleRecordClick (record) {
|
||||
if (!this.roRecordPage) return
|
||||
|
||||
const route = {
|
||||
name: 'page.record',
|
||||
params: {
|
||||
pageID: (this.roRecordPage || {}).pageID,
|
||||
recordID: record.recordID,
|
||||
},
|
||||
query: null,
|
||||
}
|
||||
|
||||
this.$router.push(route)
|
||||
},
|
||||
|
||||
refresh () {
|
||||
this.fetchRecords()
|
||||
this.pullRecords()
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
39
client/web/compose/src/mixins/records.js
Normal file
39
client/web/compose/src/mixins/records.js
Normal file
@@ -0,0 +1,39 @@
|
||||
export default {
|
||||
methods: {
|
||||
fetchRecords (namespaceID, fields = [], records = []) {
|
||||
if (records.length === 0 || fields.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const moduleRecords = {}
|
||||
|
||||
fields.filter(c => c.kind === 'Record').forEach(f => {
|
||||
const { moduleID } = f.options || {}
|
||||
if (!moduleRecords[moduleID]) {
|
||||
moduleRecords[moduleID] = new Set()
|
||||
}
|
||||
|
||||
records.forEach(r => {
|
||||
let recordIDs = []
|
||||
|
||||
if (f.isSystem) {
|
||||
recordIDs = [r[f.name]]
|
||||
} else {
|
||||
recordIDs = f.isMulti ? r.values[f.name] : [r.values[f.name]]
|
||||
}
|
||||
|
||||
recordIDs.forEach(recordID => {
|
||||
if (!recordID) return
|
||||
moduleRecords[moduleID].add(recordID)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Dispatch resolution per module
|
||||
return Promise.all(Object.entries(moduleRecords).map(([moduleID, recordIDs]) => {
|
||||
recordIDs = [...recordIDs]
|
||||
return this.$store.dispatch('record/resolveRecords', { namespaceID, moduleID, recordIDs })
|
||||
}))
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -17,7 +17,7 @@ export default {
|
||||
})
|
||||
}).flat(Infinity))
|
||||
|
||||
return this.$store.dispatch('user/fetchUsers', [...list])
|
||||
return this.$store.dispatch('user/resolveUsers', [...list])
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import module from './module'
|
||||
import page from './page'
|
||||
import pageLayout from './page-layout'
|
||||
import chart from './chart'
|
||||
import record from './record'
|
||||
import user from './user'
|
||||
import languages from './languages'
|
||||
import ui from './ui'
|
||||
@@ -22,6 +23,7 @@ export default new Vuex.Store({
|
||||
page: page(Vue.prototype.$ComposeAPI),
|
||||
pageLayout: pageLayout(Vue.prototype.$ComposeAPI),
|
||||
chart: chart(Vue.prototype.$ComposeAPI),
|
||||
record: record(Vue.prototype.$ComposeAPI),
|
||||
user: user(Vue.prototype.$SystemAPI),
|
||||
languages: languages(Vue.prototype.$SystemAPI),
|
||||
ui: ui(Vue.prototype.$ComposeAPI),
|
||||
|
||||
113
client/web/compose/src/store/record.js
Normal file
113
client/web/compose/src/store/record.js
Normal file
@@ -0,0 +1,113 @@
|
||||
// Records in this store not classes, but raw objects instead
|
||||
const types = {
|
||||
pending: 'pending',
|
||||
completed: 'completed',
|
||||
updateSet: 'updateSet',
|
||||
clearSet: 'clearSet',
|
||||
}
|
||||
|
||||
export default function (ComposeAPI) {
|
||||
return {
|
||||
namespaced: true,
|
||||
|
||||
state: {
|
||||
pending: false,
|
||||
set: [],
|
||||
},
|
||||
|
||||
getters: {
|
||||
pending: (state) => state.pending,
|
||||
|
||||
findByID (state) {
|
||||
return (ID) => state.set.find(({ recordID }) => ID === recordID)
|
||||
},
|
||||
|
||||
findByIDs (state) {
|
||||
return (IDs) => {
|
||||
IDs = IDs.flat()
|
||||
return state.set.filter(({ recordID }) => IDs.includes(recordID))
|
||||
}
|
||||
},
|
||||
|
||||
set (state) {
|
||||
return state.set
|
||||
},
|
||||
},
|
||||
|
||||
actions: {
|
||||
/**
|
||||
* Similar to fetchRecords but it only fetches unknown (not in set) ids
|
||||
*/
|
||||
async resolveRecords ({ commit, getters }, { namespaceID, moduleID, recordIDs }) {
|
||||
if (recordIDs.length === 0) {
|
||||
// save ourselves some work
|
||||
return
|
||||
}
|
||||
|
||||
// exclude existing & make unique
|
||||
const existing = new Set(getters.set.map(({ recordID }) => recordID))
|
||||
recordIDs = [...new Set(recordIDs.filter(recordID => !existing.has(recordID)))]
|
||||
|
||||
if (recordIDs.length === 0) {
|
||||
// Check for values again
|
||||
return
|
||||
}
|
||||
|
||||
const query = recordIDs.map(recordID => `recordID = ${recordID}`).join(' OR ')
|
||||
|
||||
return ComposeAPI.recordList({ namespaceID, moduleID, query, deleted: 1 }).then(({ set }) => {
|
||||
commit(types.updateSet, set)
|
||||
}).finally(() => {
|
||||
commit(types.completed)
|
||||
})
|
||||
},
|
||||
|
||||
updateRecords ({ commit }, records) {
|
||||
commit(types.updateSet, records)
|
||||
},
|
||||
|
||||
push ({ commit }, record) {
|
||||
commit(types.updateSet, record)
|
||||
},
|
||||
|
||||
clearSet ({ commit }) {
|
||||
commit(types.clearSet)
|
||||
},
|
||||
},
|
||||
|
||||
mutations: {
|
||||
[types.pending] (state) {
|
||||
state.pending = true
|
||||
},
|
||||
|
||||
[types.completed] (state) {
|
||||
state.pending = false
|
||||
},
|
||||
|
||||
[types.updateSet] (state, set) {
|
||||
set = (Array.isArray(set) ? set : [set]).filter(r => !!r)
|
||||
|
||||
if (state.set.length === 0) {
|
||||
state.set = set
|
||||
return
|
||||
}
|
||||
|
||||
const existing = new Set(state.set.map(({ recordID }) => recordID))
|
||||
|
||||
set.forEach(newItem => {
|
||||
const oldIndex = !existing.has(newItem.recordID) ? state.set.findIndex(({ recordID }) => recordID === newItem.recordID) : -1
|
||||
if (oldIndex > -1) {
|
||||
state.set.splice(oldIndex, 1, newItem)
|
||||
} else {
|
||||
state.set.push(newItem)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
[types.clearSet] (state) {
|
||||
state.pending = false
|
||||
state.set.splice(0)
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -32,9 +32,9 @@ export default function (SystemAPI) {
|
||||
},
|
||||
|
||||
actions: {
|
||||
async load ({ commit }) {
|
||||
async load ({ commit }, filter) {
|
||||
commit(types.pending)
|
||||
return SystemAPI.userList().then(({ set }) => {
|
||||
return SystemAPI.userList(filter).then(({ set }) => {
|
||||
commit(types.updateSet, set)
|
||||
}).finally(() => {
|
||||
commit(types.completed)
|
||||
|
||||
@@ -240,7 +240,6 @@ export default {
|
||||
.recordRead({ namespaceID, moduleID, recordID })
|
||||
.then(record => {
|
||||
this.record = new compose.Record(module, record)
|
||||
this.fetchUsers(this.module.fields, [this.record])
|
||||
})
|
||||
.catch(this.toastErrorHandler(this.$t('notification:record.loadFailed')))
|
||||
}
|
||||
|
||||
@@ -60,6 +60,9 @@ export default {
|
||||
},
|
||||
|
||||
created () {
|
||||
// Preload first 500 users
|
||||
this.$store.dispatch('user/load', { limit: 500 })
|
||||
|
||||
this.$store.dispatch('namespace/load', { force: true }).then(namespaces => {
|
||||
this.namespaces = namespaces
|
||||
this.loaded = true
|
||||
|
||||
@@ -274,6 +274,7 @@ export default {
|
||||
methods: {
|
||||
...mapActions({
|
||||
popPreviousPages: 'ui/popPreviousPages',
|
||||
clearRecordSet: 'record/clearSet',
|
||||
}),
|
||||
|
||||
async loadRecord () {
|
||||
@@ -403,6 +404,8 @@ export default {
|
||||
},
|
||||
|
||||
async determineLayout (pageLayoutID) {
|
||||
// Clear stored records so they can be refetched with latest values
|
||||
this.clearRecordSet()
|
||||
let expressions = {}
|
||||
|
||||
// Only evaluate if one of the layouts has an expressions variable
|
||||
|
||||
@@ -230,6 +230,7 @@ export default {
|
||||
clearRecordIDs: 'ui/clearRecordIDs',
|
||||
setPreviousPages: 'ui/setPreviousPages',
|
||||
pushPreviousPages: 'ui/pushPreviousPages',
|
||||
clearRecordSet: 'record/clearSet',
|
||||
}),
|
||||
|
||||
evaluateLayoutExpressions () {
|
||||
@@ -265,6 +266,8 @@ export default {
|
||||
},
|
||||
|
||||
async determineLayout () {
|
||||
// Clear stored records so they can be refetched with latest values
|
||||
this.clearRecordSet()
|
||||
this.layouts = this.getPageLayouts(this.page.pageID)
|
||||
|
||||
let expressions = {}
|
||||
|
||||
Reference in New Issue
Block a user