mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-17 18:32:35 +00:00
Resolve "Show related row by clicking on a link row relationship"
This commit is contained in:
parent
ee3d1161b3
commit
fa75fbbd0d
14 changed files with 562 additions and 73 deletions
changelog.md
web-frontend
modules
test/unit/database/store
|
@ -16,6 +16,8 @@
|
|||
* Added `is days ago` filter to date field.
|
||||
* Fixed a bug that made it possible to delete created on/modified by fields on the web frontend.
|
||||
* Allow the setting of max request page size via environment variable.
|
||||
* Introduced read only lookup of foreign row by clicking on a link row relationship in
|
||||
the grid view row modal.
|
||||
* Boolean field converts the word `checked` to `True` value.
|
||||
* Fixed a bug where the backend would fail hard updating token permissions for deleted tables.
|
||||
* Fixed the unchecked percent aggregation calculation
|
||||
|
|
|
@ -24,12 +24,23 @@
|
|||
@extend %ellipsis;
|
||||
|
||||
max-width: 200px;
|
||||
color: $color-primary-900;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&.field-link-row__name--unnamed {
|
||||
color: $color-neutral-600;
|
||||
}
|
||||
}
|
||||
|
||||
.field-link-row__loading {
|
||||
margin: 5px 0 0 4px;
|
||||
|
||||
@include loading(12px);
|
||||
}
|
||||
|
||||
.field-link-row__remove {
|
||||
color: $color-primary-900;
|
||||
margin-left: 5px;
|
||||
|
|
|
@ -29,9 +29,14 @@
|
|||
background-color: $color-neutral-100;
|
||||
border-radius: 3px;
|
||||
display: flex;
|
||||
color: $color-primary-900;
|
||||
|
||||
@include fixed-height(22px, 13px);
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.grid-field-many-to-many__cell.active & {
|
||||
background-color: $color-primary-100;
|
||||
|
||||
|
@ -65,6 +70,12 @@
|
|||
max-width: 140px;
|
||||
}
|
||||
|
||||
.grid-field-many-to-many__loading {
|
||||
margin: 5px 0 0 4px;
|
||||
|
||||
@include loading(12px);
|
||||
}
|
||||
|
||||
.grid-field-many-to-many__remove {
|
||||
display: none;
|
||||
color: $color-primary-900;
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
<template>
|
||||
<RowEditModal
|
||||
ref="modal"
|
||||
:read-only="true"
|
||||
:table="table"
|
||||
:rows="[]"
|
||||
:fields="fields"
|
||||
:primary="primary"
|
||||
@hidden="$emit('hidden', $event)"
|
||||
></RowEditModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { DatabaseApplicationType } from '@baserow/modules/database/applicationTypes'
|
||||
import RowEditModal from '@baserow/modules/database/components/row/RowEditModal'
|
||||
import FieldService from '@baserow/modules/database/services/field'
|
||||
import RowService from '@baserow/modules/database/services/row'
|
||||
import { populateField } from '@baserow/modules/database/store/field'
|
||||
|
||||
/**
|
||||
* This component can open the row edit modal having the fields of that table in the
|
||||
* fields store. It will make a request to the backend fetching the missing
|
||||
* information.
|
||||
*/
|
||||
export default {
|
||||
name: 'ForeignRowEditModal',
|
||||
components: { RowEditModal },
|
||||
props: {
|
||||
tableId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fetchedTableAndFields: false,
|
||||
table: {},
|
||||
fields: [],
|
||||
primary: {},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async fetchTableAndFields() {
|
||||
// Find the table in the applications to prevent a request to the backend and to
|
||||
// maintain reactivity with the real time updates.
|
||||
const databaseType = DatabaseApplicationType.getType()
|
||||
for (const application of this.$store.getters['application/getAll']) {
|
||||
if (application.type !== databaseType) {
|
||||
continue
|
||||
}
|
||||
|
||||
const foundTable = application.tables.find(
|
||||
({ id }) => id === this.tableId
|
||||
)
|
||||
|
||||
if (foundTable) {
|
||||
this.table = foundTable
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Because we don't have the fields in the store we need to fetch those for this
|
||||
// table.
|
||||
const { data: fieldData } = await FieldService(this.$client).fetchAll(
|
||||
this.tableId
|
||||
)
|
||||
fieldData.forEach((part, index) => {
|
||||
populateField(fieldData[index], this.$registry)
|
||||
})
|
||||
const primaryIndex = fieldData.findIndex((item) => item.primary === true)
|
||||
this.primary =
|
||||
primaryIndex !== -1 ? fieldData.splice(primaryIndex, 1)[0] : null
|
||||
this.fields = fieldData
|
||||
|
||||
// Mark the table and fields as fetched, so that we don't have to do that a
|
||||
// second time when the user opens another row.
|
||||
this.fetchedTableAndFields = true
|
||||
},
|
||||
|
||||
async show(rowId) {
|
||||
if (!this.fetchedTableAndFields) {
|
||||
await this.fetchTableAndFields()
|
||||
}
|
||||
|
||||
const { data: rowData } = await RowService(this.$client).get(
|
||||
this.tableId,
|
||||
rowId
|
||||
)
|
||||
this.$refs.modal.show(rowData.id, rowData)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -2,19 +2,25 @@
|
|||
<div class="control__elements">
|
||||
<ul class="field-link-row__items">
|
||||
<li v-for="item in value" :key="item.id" class="field-link-row__item">
|
||||
<span
|
||||
<component
|
||||
:is="readOnly ? 'span' : 'a'"
|
||||
class="field-link-row__name"
|
||||
:class="{
|
||||
'field-link-row__name--unnamed':
|
||||
item.value === null || item.value === '',
|
||||
}"
|
||||
@click.prevent="showForeignRowModal(item)"
|
||||
>
|
||||
{{ item.value || 'unnamed row ' + item.id }}
|
||||
</span>
|
||||
</component>
|
||||
<span
|
||||
v-if="itemLoadingId === item.id"
|
||||
class="field-link-row__loading"
|
||||
></span>
|
||||
<a
|
||||
v-if="!readOnly"
|
||||
v-else-if="!readOnly"
|
||||
class="field-link-row__remove"
|
||||
@click.prevent="removeValue($event, value, item.id)"
|
||||
@click.prevent.stop="removeValue($event, value, item.id)"
|
||||
>
|
||||
<i class="fas fa-times"></i>
|
||||
</a>
|
||||
|
@ -34,6 +40,11 @@
|
|||
:value="value"
|
||||
@selected="addValue(value, $event)"
|
||||
></SelectRowModal>
|
||||
<ForeignRowEditModal
|
||||
ref="rowEditModal"
|
||||
:table-id="field.link_row_table"
|
||||
@hidden="modalOpen = false"
|
||||
></ForeignRowEditModal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -41,10 +52,17 @@
|
|||
import rowEditField from '@baserow/modules/database/mixins/rowEditField'
|
||||
import linkRowField from '@baserow/modules/database/mixins/linkRowField'
|
||||
import SelectRowModal from '@baserow/modules/database/components/row/SelectRowModal'
|
||||
import ForeignRowEditModal from '@baserow/modules/database/components/row/ForeignRowEditModal'
|
||||
import { notifyIf } from '@baserow/modules/core/utils/error'
|
||||
|
||||
export default {
|
||||
components: { SelectRowModal },
|
||||
components: { SelectRowModal, ForeignRowEditModal },
|
||||
mixins: [rowEditField, linkRowField],
|
||||
data() {
|
||||
return {
|
||||
itemLoadingId: -1,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
removeValue(...args) {
|
||||
linkRowField.methods.removeValue.call(this, ...args)
|
||||
|
@ -54,6 +72,19 @@ export default {
|
|||
linkRowField.methods.addValue.call(this, ...args)
|
||||
this.touch()
|
||||
},
|
||||
async showForeignRowModal(item) {
|
||||
if (this.readOnly) {
|
||||
return
|
||||
}
|
||||
|
||||
this.itemLoadingId = item.id
|
||||
try {
|
||||
await this.$refs.rowEditModal.show(item.id)
|
||||
} catch (error) {
|
||||
notifyIf(error)
|
||||
}
|
||||
this.itemLoadingId = -1
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -50,8 +50,6 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
import modal from '@baserow/modules/core/mixins/modal'
|
||||
import RowEditModalField from '@baserow/modules/database/components/row/RowEditModalField'
|
||||
import CreateFieldContext from '@baserow/modules/database/components/field/CreateFieldContext'
|
||||
|
@ -94,17 +92,24 @@ export default {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
rowId: 'rowModal/id',
|
||||
rowExists: 'rowModal/exists',
|
||||
row: 'rowModal/row',
|
||||
}),
|
||||
modalRow() {
|
||||
return this.$store.getters['rowModal/get'](this._uid)
|
||||
},
|
||||
rowId() {
|
||||
return this.modalRow.id
|
||||
},
|
||||
rowExists() {
|
||||
return this.modalRow.exists
|
||||
},
|
||||
row() {
|
||||
return this.modalRow.row
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
/**
|
||||
* It could happen that the view doesn't always have all the rows buffered. When
|
||||
* the modal is opened, it will find the correct row by looking through all the
|
||||
* rows of the view. If a filter changes, the existing row could be removed the
|
||||
* rows of the view. If a filter changes, the existing row could be removed from the
|
||||
* buffer while the user still wants to edit the row because the modal is open. In
|
||||
* that case, we will keep a copy in the `rowModal` store, which will also listen
|
||||
* for real time update events to make sure the latest information is always
|
||||
|
@ -114,28 +119,38 @@ export default {
|
|||
rows(value) {
|
||||
const row = value.find((r) => r !== null && r.id === this.rowId)
|
||||
if (row === undefined && this.rowExists) {
|
||||
this.$store.dispatch('rowModal/doesNotExist')
|
||||
this.$store.dispatch('rowModal/doesNotExist', {
|
||||
componentId: this._uid,
|
||||
})
|
||||
} else if (row !== undefined && !this.rowExists) {
|
||||
this.$store.dispatch('rowModal/doesExist', { row })
|
||||
this.$store.dispatch('rowModal/doesExist', {
|
||||
componentId: this._uid,
|
||||
row,
|
||||
})
|
||||
} else if (row !== undefined) {
|
||||
// If the row already exists and it has changed, we need to replace it,
|
||||
// otherwise we might loose reactivity.
|
||||
this.$store.dispatch('rowModal/replace', { row })
|
||||
this.$store.dispatch('rowModal/replace', {
|
||||
componentId: this._uid,
|
||||
row,
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
show(rowId, ...args) {
|
||||
show(rowId, rowFallback = {}, ...args) {
|
||||
const row = this.rows.find((r) => r !== null && r.id === rowId)
|
||||
this.$store.dispatch('rowModal/open', {
|
||||
tableId: this.table.id,
|
||||
componentId: this._uid,
|
||||
id: rowId,
|
||||
row: row || {},
|
||||
row: row || rowFallback,
|
||||
exists: !!row,
|
||||
})
|
||||
this.getRootModal().show(...args)
|
||||
},
|
||||
hide(...args) {
|
||||
this.$store.dispatch('rowModal/clear')
|
||||
this.$store.dispatch('rowModal/clear', { componentId: this._uid })
|
||||
this.getRootModal().hide(...args)
|
||||
},
|
||||
/**
|
||||
|
|
|
@ -52,6 +52,7 @@
|
|||
:field="props.field"
|
||||
:value="props.row['field_' + props.field.id]"
|
||||
:selected="parent.isCellSelected(props.field.id)"
|
||||
:store-prefix="props.storePrefix"
|
||||
:read-only="props.readOnly"
|
||||
@update="(...args) => $options.methods.update(listeners, props, ...args)"
|
||||
@edit="(...args) => $options.methods.edit(listeners, props, ...args)"
|
||||
|
|
|
@ -76,10 +76,10 @@
|
|||
</div>
|
||||
</template>
|
||||
<!--
|
||||
Somehow re-declaring all the events instead of using v-on="$listeners" speeds
|
||||
everything up because the rows don't need to be updated everytime a new one is
|
||||
rendered, which happens a lot when scrolling.
|
||||
-->
|
||||
Somehow re-declaring all the events instead of using v-on="$listeners" speeds
|
||||
everything up because the rows don't need to be updated everytime a new one is
|
||||
rendered, which happens a lot when scrolling.
|
||||
-->
|
||||
<GridViewCell
|
||||
v-for="field in fieldsToRender"
|
||||
:key="'row-field-' + row.id.toString() + '-' + field.id.toString()"
|
||||
|
@ -88,6 +88,7 @@
|
|||
:state="state"
|
||||
:multi-select-position="getMultiSelectPosition(row.id, field)"
|
||||
:read-only="readOnly"
|
||||
:store-prefix="storePrefix"
|
||||
:style="{
|
||||
width: fieldWidths[field.id] + 'px',
|
||||
...getSelectedCellStyle(field),
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
<template>
|
||||
<div class="grid-view__cell grid-field-many-to-many__cell active">
|
||||
<div class="grid-field-many-to-many__list">
|
||||
<div
|
||||
<component
|
||||
:is="publicGrid || readOnly ? 'span' : 'a'"
|
||||
v-for="item in value"
|
||||
:key="item.id"
|
||||
class="grid-field-many-to-many__item"
|
||||
@click.prevent="showForeignRowModal(item)"
|
||||
>
|
||||
<span
|
||||
class="grid-field-many-to-many__name"
|
||||
|
@ -17,14 +19,18 @@
|
|||
item.value || $t('gridViewFieldLinkRow.unnamed', { value: item.id })
|
||||
}}
|
||||
</span>
|
||||
<span
|
||||
v-if="itemLoadingId === item.id"
|
||||
class="grid-field-many-to-many__loading"
|
||||
></span>
|
||||
<a
|
||||
v-if="!readOnly"
|
||||
v-else-if="!readOnly"
|
||||
class="grid-field-many-to-many__remove"
|
||||
@click.prevent="removeValue($event, value, item.id)"
|
||||
@click.prevent.stop="removeValue($event, value, item.id)"
|
||||
>
|
||||
<i class="fas fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
</component>
|
||||
<a
|
||||
v-if="!readOnly"
|
||||
class="
|
||||
|
@ -42,22 +48,40 @@
|
|||
@selected="addValue(value, $event)"
|
||||
@hidden="hideModal"
|
||||
></SelectRowModal>
|
||||
<ForeignRowEditModal
|
||||
ref="rowEditModal"
|
||||
:table-id="field.link_row_table"
|
||||
@hidden="hideModal"
|
||||
></ForeignRowEditModal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
import { isElement } from '@baserow/modules/core/utils/dom'
|
||||
import gridField from '@baserow/modules/database/mixins/gridField'
|
||||
import linkRowField from '@baserow/modules/database/mixins/linkRowField'
|
||||
import SelectRowModal from '@baserow/modules/database/components/row/SelectRowModal'
|
||||
import ForeignRowEditModal from '@baserow/modules/database/components/row/ForeignRowEditModal'
|
||||
import { notifyIf } from '@baserow/modules/core/utils/error'
|
||||
|
||||
export default {
|
||||
name: 'GridViewFieldLinkRow',
|
||||
components: { SelectRowModal },
|
||||
components: { ForeignRowEditModal, SelectRowModal },
|
||||
mixins: [gridField, linkRowField],
|
||||
data() {
|
||||
return {
|
||||
modalOpen: false,
|
||||
itemLoadingId: -1,
|
||||
}
|
||||
},
|
||||
beforeCreate() {
|
||||
this.$options.computed = {
|
||||
...(this.$options.computed || {}),
|
||||
...mapGetters({
|
||||
publicGrid: this.$options.propsData.storePrefix + 'view/grid/isPublic',
|
||||
}),
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -81,7 +105,10 @@ export default {
|
|||
* inside one of these contexts.
|
||||
*/
|
||||
canUnselectByClickingOutside(event) {
|
||||
return !isElement(this.$refs.selectModal.$el, event.target)
|
||||
return (
|
||||
!isElement(this.$refs.selectModal.$el, event.target) &&
|
||||
!isElement(this.$refs.rowEditModal.$refs.modal.$el, event.target)
|
||||
)
|
||||
},
|
||||
/**
|
||||
* Prevent unselecting the field cell by changing the event. Because the deleted
|
||||
|
@ -107,7 +134,7 @@ export default {
|
|||
* While the modal is open, all key combinations related to the field must be
|
||||
* ignored.
|
||||
*/
|
||||
canKeyDown(event) {
|
||||
canKeyDown() {
|
||||
return !this.modalOpen
|
||||
},
|
||||
canPaste() {
|
||||
|
@ -119,6 +146,22 @@ export default {
|
|||
canEmpty() {
|
||||
return !this.modalOpen
|
||||
},
|
||||
async showForeignRowModal(item) {
|
||||
// It's not possible to open the related row when the view is shared publicly
|
||||
// because the visitor doesn't have the right permissions.
|
||||
if (this.publicGrid || this.readOnly) {
|
||||
return
|
||||
}
|
||||
|
||||
this.itemLoadingId = item.id
|
||||
try {
|
||||
await this.$refs.rowEditModal.show(item.id)
|
||||
this.modalOpen = true
|
||||
} catch (error) {
|
||||
notifyIf(error)
|
||||
}
|
||||
this.itemLoadingId = -1
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -21,6 +21,10 @@ export default {
|
|||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
storePrefix: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
|
@ -180,7 +180,10 @@ export const registerRealtimeEvents = (realtime) => {
|
|||
)
|
||||
}
|
||||
|
||||
store.dispatch('rowModal/updated', { values: data.row })
|
||||
store.dispatch('rowModal/updated', {
|
||||
tableId: data.table_id,
|
||||
values: data.row,
|
||||
})
|
||||
})
|
||||
|
||||
realtime.registerEvent('rows_updated', async (context, data) => {
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
export default (client) => {
|
||||
return {
|
||||
get(tableId, rowId) {
|
||||
return client.get(`/database/rows/table/${tableId}/${rowId}/`)
|
||||
},
|
||||
fetchAll({ tableId, page = 1, size = 10, search = null }) {
|
||||
const config = {
|
||||
params: {
|
||||
|
|
|
@ -1,73 +1,123 @@
|
|||
import Vue from 'vue'
|
||||
|
||||
/**
|
||||
* This store exists to always keep a copy of the row that's being edited via the
|
||||
* row edit modal. It sometimes happen that row from the original source, where it was
|
||||
* reactive with doesn't exist anymore. To make sure the modal still works in that
|
||||
* case, we always store a copy here and if it doesn't exist in the original data
|
||||
* source it accepts real time updates.
|
||||
* source it accepts real time updates. This store can handle multiple row edit
|
||||
* modals being open because the rows are divided by the unique component id.
|
||||
*/
|
||||
export const state = () => ({
|
||||
id: -1,
|
||||
exists: false,
|
||||
row: {},
|
||||
// The key of the rows property is the unique component id indicating to which row
|
||||
// edit modal the entry is related to. The value looks like:
|
||||
// {
|
||||
// tableId: -1,
|
||||
// // row id
|
||||
// id: -1,
|
||||
// // Indicates whether the row exists in the `rows` property in the row edit modal.
|
||||
// exists: true,
|
||||
// // The values of the row.
|
||||
// row: {}
|
||||
// }
|
||||
rows: {},
|
||||
})
|
||||
|
||||
export const mutations = {
|
||||
CLEAR(state) {
|
||||
state.id = -1
|
||||
state.exists = false
|
||||
state.row = {}
|
||||
CLEAR(state, componentId) {
|
||||
delete state.rows[componentId]
|
||||
},
|
||||
OPEN(state, { id, exists, row }) {
|
||||
state.id = id
|
||||
state.exists = exists
|
||||
state.row = row
|
||||
OPEN(state, { componentId, tableId, id, exists, row }) {
|
||||
state.rows = {
|
||||
...state.rows,
|
||||
...{
|
||||
[componentId]: {
|
||||
tableId,
|
||||
id,
|
||||
exists,
|
||||
row,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
SET_EXISTS(state, value) {
|
||||
state.exists = value
|
||||
SET_EXISTS(state, { componentId, value }) {
|
||||
state.rows[componentId] = {
|
||||
...state.rows[componentId],
|
||||
...{ exists: value },
|
||||
}
|
||||
},
|
||||
REPLACE_ROW(state, row) {
|
||||
Vue.set(state, 'row', row)
|
||||
REPLACE_ROW(state, { componentId, row }) {
|
||||
state.rows[componentId] = {
|
||||
...state.rows[componentId],
|
||||
...{ row },
|
||||
}
|
||||
},
|
||||
UPDATE_ROW(state, row) {
|
||||
Object.assign(state.row, row)
|
||||
UPDATE_ROW(state, { componentId, row }) {
|
||||
Object.assign(state.rows[componentId].row, row)
|
||||
},
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
clear({ commit }) {
|
||||
commit('CLEAR')
|
||||
clear({ commit }, { componentId }) {
|
||||
commit('CLEAR', componentId)
|
||||
},
|
||||
open({ commit }, { id, exists, row }) {
|
||||
commit('OPEN', { id, exists, row })
|
||||
/**
|
||||
* Is called when the row edit modal is being opened. It will register the row
|
||||
* values in this store so that it can also receive real time updates if it's
|
||||
* managed by the `rows` prop in the row edit modal.
|
||||
*/
|
||||
open({ commit }, { componentId, tableId, id, exists, row }) {
|
||||
commit('OPEN', { componentId, tableId, id, exists, row })
|
||||
},
|
||||
doesNotExist({ commit }) {
|
||||
commit('SET_EXISTS', false)
|
||||
/**
|
||||
* Marking the row as does not exist makes it managed by this store instead of the
|
||||
* provided rows. This will make sure that it accepts real time update events.
|
||||
*/
|
||||
doesNotExist({ commit }, { componentId }) {
|
||||
commit('SET_EXISTS', { componentId, value: false })
|
||||
},
|
||||
doesExist({ commit }, { row }) {
|
||||
commit('SET_EXISTS', true)
|
||||
commit('REPLACE_ROW', row)
|
||||
doesExist({ commit }, { componentId, row }) {
|
||||
commit('SET_EXISTS', { componentId, value: true })
|
||||
commit('REPLACE_ROW', { componentId, row })
|
||||
},
|
||||
replace({ commit }, { row }) {
|
||||
commit('REPLACE_ROW', row)
|
||||
replace({ commit }, { componentId, row }) {
|
||||
commit('REPLACE_ROW', { componentId, row })
|
||||
},
|
||||
updated({ commit, getters }, { values }) {
|
||||
if (values.id === getters.id && !getters.exists) {
|
||||
commit('UPDATE_ROW', values)
|
||||
}
|
||||
/**
|
||||
* Called when we receive a real time row update event. It loops over all the rows
|
||||
* we have in memory here and checks if the updated row exists and if it's not
|
||||
* managed by the `rows` prop in the row edit modal. If so, it will make the
|
||||
* update. If the row is managed by the `rows` prop we don't have to do the update
|
||||
* because it will be done via `rows` property.
|
||||
*/
|
||||
updated({ commit, getters }, { tableId, values }) {
|
||||
const rows = getters.getRows
|
||||
Object.keys(rows).forEach((key) => {
|
||||
const value = rows[key]
|
||||
if (
|
||||
value.tableId === tableId &&
|
||||
value.id === values.id &&
|
||||
!value.exists
|
||||
) {
|
||||
commit('UPDATE_ROW', { componentId: key, row: values })
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
export const getters = {
|
||||
id: (state) => {
|
||||
return state.id
|
||||
getRows(state) {
|
||||
return state.rows
|
||||
},
|
||||
exists: (state) => {
|
||||
return state.exists
|
||||
},
|
||||
row: (state) => {
|
||||
return state.row
|
||||
get: (state) => (componentId) => {
|
||||
if (!Object.prototype.hasOwnProperty.call(state.rows, componentId)) {
|
||||
return {
|
||||
id: -1,
|
||||
tableId: -1,
|
||||
exists: false,
|
||||
row: {},
|
||||
}
|
||||
}
|
||||
|
||||
return state.rows[componentId]
|
||||
},
|
||||
}
|
||||
|
||||
|
|
221
web-frontend/test/unit/database/store/rowModal.spec.js
Normal file
221
web-frontend/test/unit/database/store/rowModal.spec.js
Normal file
|
@ -0,0 +1,221 @@
|
|||
import rowModal from '@baserow/modules/database/store/rowModal'
|
||||
import { TestApp } from '@baserow/test/helpers/testApp'
|
||||
|
||||
describe('rowModal store', () => {
|
||||
let testApp = null
|
||||
let store = null
|
||||
|
||||
beforeEach(() => {
|
||||
testApp = new TestApp()
|
||||
store = testApp.store
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
testApp.afterEach()
|
||||
})
|
||||
|
||||
test('get not existing component id', () => {
|
||||
const testStore = rowModal
|
||||
const state = Object.assign(testStore.state(), {})
|
||||
testStore.state = () => state
|
||||
store.registerModule('test', testStore)
|
||||
|
||||
const values = store.getters['test/get'](-1)
|
||||
expect(values).toMatchObject({
|
||||
id: -1,
|
||||
tableId: -1,
|
||||
exists: false,
|
||||
row: {},
|
||||
})
|
||||
})
|
||||
|
||||
test('open row', async () => {
|
||||
const testStore = rowModal
|
||||
const state = Object.assign(testStore.state(), {})
|
||||
testStore.state = () => state
|
||||
store.registerModule('test', testStore)
|
||||
await store.dispatch('test/open', {
|
||||
componentId: 1,
|
||||
tableId: 10,
|
||||
id: 100,
|
||||
exists: true,
|
||||
row: { id: 100, field_1: 'Test' },
|
||||
})
|
||||
await store.dispatch('test/open', {
|
||||
componentId: 2,
|
||||
tableId: 20,
|
||||
id: 200,
|
||||
exists: true,
|
||||
row: { id: 200, field_2: 'Test' },
|
||||
})
|
||||
const valuesOfComponent1 = store.getters['test/get'](1)
|
||||
expect(valuesOfComponent1).toMatchObject({
|
||||
tableId: 10,
|
||||
id: 100,
|
||||
exists: true,
|
||||
row: { id: 100, field_1: 'Test' },
|
||||
})
|
||||
const valuesOfComponent2 = store.getters['test/get'](2)
|
||||
expect(valuesOfComponent2).toMatchObject({
|
||||
tableId: 20,
|
||||
id: 200,
|
||||
exists: true,
|
||||
row: { id: 200, field_2: 'Test' },
|
||||
})
|
||||
})
|
||||
|
||||
test('open row', async () => {
|
||||
const testStore = rowModal
|
||||
const state = Object.assign(testStore.state(), {})
|
||||
testStore.state = () => state
|
||||
store.registerModule('test', testStore)
|
||||
await store.dispatch('test/open', {
|
||||
componentId: 1,
|
||||
tableId: 10,
|
||||
id: 100,
|
||||
exists: true,
|
||||
row: { id: 100, field_1: 'Test' },
|
||||
})
|
||||
await store.dispatch('test/open', {
|
||||
componentId: 2,
|
||||
tableId: 20,
|
||||
id: 200,
|
||||
exists: true,
|
||||
row: { id: 200, field_2: 'Test' },
|
||||
})
|
||||
const valuesOfComponent1 = store.getters['test/get'](1)
|
||||
expect(valuesOfComponent1).toMatchObject({
|
||||
tableId: 10,
|
||||
id: 100,
|
||||
exists: true,
|
||||
row: { id: 100, field_1: 'Test' },
|
||||
})
|
||||
const valuesOfComponent2 = store.getters['test/get'](2)
|
||||
expect(valuesOfComponent2).toMatchObject({
|
||||
tableId: 20,
|
||||
id: 200,
|
||||
exists: true,
|
||||
row: { id: 200, field_2: 'Test' },
|
||||
})
|
||||
})
|
||||
|
||||
test('clear row', async () => {
|
||||
const testStore = rowModal
|
||||
const state = Object.assign(testStore.state(), {})
|
||||
testStore.state = () => state
|
||||
store.registerModule('test', testStore)
|
||||
await store.dispatch('test/open', {
|
||||
componentId: 1,
|
||||
tableId: 10,
|
||||
id: 100,
|
||||
exists: true,
|
||||
row: { id: 100, field_1: 'Test' },
|
||||
})
|
||||
await store.dispatch('test/clear', {
|
||||
componentId: 1,
|
||||
})
|
||||
const valuesOfComponent1 = store.getters['test/get'](1)
|
||||
expect(valuesOfComponent1).toMatchObject({
|
||||
id: -1,
|
||||
tableId: -1,
|
||||
exists: false,
|
||||
row: {},
|
||||
})
|
||||
// Clearing a component id that doesn't exist shouldn't fail.
|
||||
await store.dispatch('test/clear', {
|
||||
componentId: 2,
|
||||
})
|
||||
})
|
||||
|
||||
test('row does not exist', async () => {
|
||||
const testStore = rowModal
|
||||
const state = Object.assign(testStore.state(), {})
|
||||
testStore.state = () => state
|
||||
store.registerModule('test', testStore)
|
||||
await store.dispatch('test/open', {
|
||||
componentId: 1,
|
||||
tableId: 10,
|
||||
id: 100,
|
||||
exists: true,
|
||||
row: { id: 100, field_1: 'Test' },
|
||||
})
|
||||
await store.dispatch('test/doesNotExist', {
|
||||
componentId: 1,
|
||||
})
|
||||
const valuesOfComponent1 = store.getters['test/get'](1)
|
||||
expect(valuesOfComponent1.exists).toBe(false)
|
||||
})
|
||||
|
||||
test('row exists', async () => {
|
||||
const testStore = rowModal
|
||||
const state = Object.assign(testStore.state(), {})
|
||||
testStore.state = () => state
|
||||
store.registerModule('test', testStore)
|
||||
await store.dispatch('test/open', {
|
||||
componentId: 1,
|
||||
tableId: 10,
|
||||
id: 100,
|
||||
exists: false,
|
||||
row: { id: 100, field_1: 'Test' },
|
||||
})
|
||||
const row = { id: 100, field_1: 'Test' }
|
||||
await store.dispatch('test/doesExist', {
|
||||
componentId: 1,
|
||||
row,
|
||||
})
|
||||
// Changing this row object should be reflected in the row in the store because
|
||||
// it's the same object.
|
||||
row.field_1 = 'Test 2'
|
||||
const valuesOfComponent1 = store.getters['test/get'](1)
|
||||
expect(valuesOfComponent1.row.field_1).toBe('Test 2')
|
||||
})
|
||||
|
||||
test('row exists', async () => {
|
||||
const testStore = rowModal
|
||||
const state = Object.assign(testStore.state(), {})
|
||||
testStore.state = () => state
|
||||
store.registerModule('test', testStore)
|
||||
await store.dispatch('test/open', {
|
||||
componentId: 1,
|
||||
tableId: 10,
|
||||
id: 100,
|
||||
exists: true,
|
||||
row: { id: 100, field_1: 'Test' },
|
||||
})
|
||||
|
||||
// Because `exists` is true, the row shouldn't be updated because it's managed
|
||||
// via another process.
|
||||
await store.dispatch('test/updated', {
|
||||
tableId: 10,
|
||||
values: { id: 100, field_1: 'Test 2' },
|
||||
})
|
||||
let valuesOfComponent1 = store.getters['test/get'](1)
|
||||
expect(valuesOfComponent1.row.field_1).toBe('Test')
|
||||
|
||||
// Because `exists` is false, the row is managed by the store, so when an update
|
||||
// action is dispatched it should update the row.
|
||||
await store.dispatch('test/doesNotExist', { componentId: 1 })
|
||||
await store.dispatch('test/updated', {
|
||||
tableId: 10,
|
||||
values: { id: 100, field_1: 'Test 2' },
|
||||
})
|
||||
valuesOfComponent1 = store.getters['test/get'](1)
|
||||
expect(valuesOfComponent1.row.field_1).toBe('Test 2')
|
||||
|
||||
// Because the table id doesn't match, we don't expect the value to be updated.
|
||||
await store.dispatch('test/updated', {
|
||||
tableId: 11,
|
||||
values: { id: 100, field_1: 'Test 3' },
|
||||
})
|
||||
valuesOfComponent1 = store.getters['test/get'](1)
|
||||
expect(valuesOfComponent1.row.field_1).toBe('Test 2')
|
||||
|
||||
// Because the row id doesn't match, we dont't expect the value to be updated.
|
||||
await store.dispatch('test/updated', {
|
||||
tableId: 10,
|
||||
values: { id: 101, field_1: 'Test 4' },
|
||||
})
|
||||
valuesOfComponent1 = store.getters['test/get'](1)
|
||||
expect(valuesOfComponent1.row.field_1).toBe('Test 2')
|
||||
})
|
||||
})
|
Loading…
Add table
Reference in a new issue