mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-14 09:08:32 +00:00
Merge branch '1912-row-change-history' into 'develop'
Introduce row edit history tab Closes #1912 See merge request baserow/baserow!1594
This commit is contained in:
commit
46121e5591
21 changed files with 663 additions and 39 deletions
enterprise/web-frontend/modules/baserow_enterprise
premium/web-frontend/modules/baserow_premium
web-frontend/modules
core
assets/scss/components
components
database
|
@ -12,7 +12,6 @@ import {
|
|||
GitLabAuthProviderType,
|
||||
OpenIdConnectAuthProviderType,
|
||||
} from '@baserow_enterprise/authProviderTypes'
|
||||
|
||||
import { TeamsWorkspaceSettingsPageType } from '@baserow_enterprise/workspaceSettingsPageTypes'
|
||||
import { EnterpriseMembersPagePluginType } from '@baserow_enterprise/membersPagePluginTypes'
|
||||
import en from '@baserow_enterprise/locales/en.json'
|
||||
|
@ -25,7 +24,6 @@ import {
|
|||
EnterpriseWithoutSupportLicenseType,
|
||||
EnterpriseLicenseType,
|
||||
} from '@baserow_enterprise/licenseTypes'
|
||||
|
||||
import { EnterprisePlugin } from '@baserow_enterprise/plugins'
|
||||
|
||||
export default (context) => {
|
||||
|
|
|
@ -1,18 +1,7 @@
|
|||
import RowCommentsSidebar from '@baserow_premium/components/row_comments/RowCommentsSidebar'
|
||||
import { DatabaseApplicationType } from '@baserow/modules/database/applicationTypes'
|
||||
import GridViewRowExpandButtonWithCommentCount from '@baserow_premium/components/row_comments/GridViewRowExpandButtonWithCommentCount'
|
||||
|
||||
export class PremiumDatabaseApplicationType extends DatabaseApplicationType {
|
||||
getRowEditModalRightSidebarComponent(database, table) {
|
||||
return this.app.$hasPermission(
|
||||
'database.table.list_comments',
|
||||
table,
|
||||
database.workspace.id
|
||||
)
|
||||
? RowCommentsSidebar
|
||||
: null
|
||||
}
|
||||
|
||||
getRowExpandButtonComponent() {
|
||||
return GridViewRowExpandButtonWithCommentCount
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
"noComment": "No comments for this row yet. Use the form below to add a comment.",
|
||||
"comment": "Comment",
|
||||
"more": "More information",
|
||||
"name": "Row comments"
|
||||
"name": "Comments"
|
||||
},
|
||||
"rowComment": {
|
||||
"you": "You",
|
||||
|
|
|
@ -39,6 +39,7 @@ import { PremiumLicenseType } from '@baserow_premium/licenseTypes'
|
|||
import { PersonalViewOwnershipType } from '@baserow_premium/viewOwnershipTypes'
|
||||
import { ViewOwnershipPermissionManagerType } from '@baserow_premium/permissionManagerTypes'
|
||||
import { RowCommentMentionNotificationType } from '@baserow_premium/notificationTypes'
|
||||
import { CommentsRowModalSidebarType } from '@baserow_premium/rowModalSidebarTypes'
|
||||
|
||||
export default (context) => {
|
||||
const { store, app, isDev } = context
|
||||
|
@ -133,4 +134,9 @@ export default (context) => {
|
|||
'notification',
|
||||
new RowCommentMentionNotificationType(context)
|
||||
)
|
||||
|
||||
app.$registry.register(
|
||||
'rowModalSidebar',
|
||||
new CommentsRowModalSidebarType(context)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
import { RowModalSidebarType } from '@baserow/modules/database/rowModalSidebarTypes'
|
||||
import RowCommentsSidebar from '@baserow_premium/components/row_comments/RowCommentsSidebar'
|
||||
import PremiumFeatures from '@baserow_premium/features'
|
||||
|
||||
export class CommentsRowModalSidebarType extends RowModalSidebarType {
|
||||
static getType() {
|
||||
return 'comments'
|
||||
}
|
||||
|
||||
getName() {
|
||||
return this.app.i18n.t('rowCommentSidebar.name')
|
||||
}
|
||||
|
||||
getComponent() {
|
||||
return RowCommentsSidebar
|
||||
}
|
||||
|
||||
isDeactivated(database, table) {
|
||||
return !this.app.$hasPermission(
|
||||
'database.table.list_comments',
|
||||
table,
|
||||
database.workspace.id
|
||||
)
|
||||
}
|
||||
|
||||
isSelectedByDefault(database) {
|
||||
return this.app.$hasFeature(PremiumFeatures.PREMIUM, database.workspace.id)
|
||||
}
|
||||
|
||||
getOrder() {
|
||||
return 0
|
||||
}
|
||||
}
|
|
@ -106,6 +106,9 @@
|
|||
@import 'snapshots_modal';
|
||||
@import 'import_modal';
|
||||
@import 'row_edit_modal';
|
||||
@import 'row_edit_modal_sidebar';
|
||||
@import 'row_history';
|
||||
@import 'row_history_entry';
|
||||
@import 'data_table';
|
||||
@import 'auth';
|
||||
@import 'expand_on_overflow_list';
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
.row-edit-modal-sidebar {
|
||||
background-color: $palette-neutral-25;
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
.row-history {
|
||||
@include absolute(0, 0, 0, 0);
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.row-history__body {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
border-top-left-radius: 6px;
|
||||
|
||||
.infinite-scroll {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.row-history__empty {
|
||||
text-align: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
margin: 0 30px 0 30px;
|
||||
height: 100%;
|
||||
border-top-left-radius: 6px;
|
||||
}
|
||||
|
||||
.row-history__empty-icon {
|
||||
font-size: 30px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.row-history__empty-text {
|
||||
font-size: 14px;
|
||||
line-height: 160%;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.row-history__loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 20px 0;
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
.row-history-entry {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.row-history-entry__header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.row-history-entry__content {
|
||||
padding: 10px 14px 10px 14px;
|
||||
border: 1px solid #d9dbde;
|
||||
border-radius: 3px;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.row-history-entry__name {
|
||||
font-size: 12px;
|
||||
height: 14px;
|
||||
margin-left: 8px;
|
||||
color: #062e47;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.row_history-entry__initials {
|
||||
flex: 0 0 24px;
|
||||
font-weight: bold;
|
||||
color: $white;
|
||||
background-color: $color-primary-500;
|
||||
border-radius: 100%;
|
||||
margin-left: 0;
|
||||
|
||||
@include center-text(24px, 12px);
|
||||
}
|
||||
|
||||
.row-history-entry__timestamp {
|
||||
@extend %ellipsis;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: $color-neutral-400;
|
||||
// Prevent long characters like 'g' in the time display being cut off
|
||||
// partway down.
|
||||
height: 14px;
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
.row-history-entry__field {
|
||||
font-weight: 600;
|
||||
font-size: 10px;
|
||||
color: $color-neutral-600;
|
||||
margin-bottom: 10px;
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-top: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.row-history-entry__field-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.row-history-entry__diff {
|
||||
display: inline-block;
|
||||
padding: 2px 4px 4px 4px;
|
||||
border-radius: 3px;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 18px; /* 150% */
|
||||
|
||||
&:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.row-history-entry__diff--removed {
|
||||
background-color: #f5d3ce;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.row-history-entry__diff--added {
|
||||
background-color: #c2f0d3;
|
||||
}
|
|
@ -27,6 +27,11 @@
|
|||
gap: 24px;
|
||||
padding: 0 0 0 40px;
|
||||
}
|
||||
|
||||
&.tabs__header--full-width {
|
||||
gap: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.tabs__item {
|
||||
|
@ -50,6 +55,11 @@
|
|||
|
||||
@include absolute(auto, 0, -1px, 0);
|
||||
}
|
||||
|
||||
&.tabs__item--full-width {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.tabs__link {
|
||||
|
|
|
@ -7,7 +7,13 @@
|
|||
'tabs--large': large,
|
||||
}"
|
||||
>
|
||||
<ul class="tabs__header">
|
||||
<ul
|
||||
v-if="!collapseOneTab || tabs.length > 1"
|
||||
class="tabs__header"
|
||||
:class="{
|
||||
'tabs__header--full-width': fullWidthHeader,
|
||||
}"
|
||||
>
|
||||
<li
|
||||
v-for="(tab, index) in tabs"
|
||||
:key="tab.title"
|
||||
|
@ -16,6 +22,7 @@
|
|||
:class="{
|
||||
'tabs__item--active': isActive(index),
|
||||
'tabs__item--disabled': tab.disabled,
|
||||
'tabs__item--full-width': fullWidthHeader,
|
||||
}"
|
||||
@click="tab.disabled ? null : selectTab(index)"
|
||||
>
|
||||
|
@ -62,6 +69,16 @@ export default {
|
|||
required: false,
|
||||
default: false,
|
||||
},
|
||||
fullWidthHeader: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
collapseOneTab: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
|
@ -11,18 +11,6 @@ export class DatabaseApplicationType extends ApplicationType {
|
|||
return 'database'
|
||||
}
|
||||
|
||||
/**
|
||||
* By default there is no right sidebar in the row edit modal. Override this method
|
||||
* and provide a Sidebar component class if you wish there to be one. This component
|
||||
* will be provided two props row and table.
|
||||
*
|
||||
* @return The component to use as the row edit modal's right sidebar or null to not
|
||||
* use one.
|
||||
*/
|
||||
getRowEditModalRightSidebarComponent(database, table) {
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The component to use as the button the user clicks to expand and view the
|
||||
* row edit modal for a particular row. Takes a single row prop and should emit a
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<template>
|
||||
<Modal
|
||||
ref="modal"
|
||||
:full-height="!!optionalRightSideBar"
|
||||
:right-sidebar="!!optionalRightSideBar"
|
||||
:content-scrollable="!!optionalRightSideBar"
|
||||
:full-height="hasRightSidebar"
|
||||
:right-sidebar="hasRightSidebar"
|
||||
:content-scrollable="hasRightSidebar"
|
||||
:right-sidebar-scrollable="false"
|
||||
:collapsible-right-sidebar="true"
|
||||
@hidden="$emit('hidden', { row })"
|
||||
|
@ -92,13 +92,12 @@
|
|||
></CreateFieldContext>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="!!optionalRightSideBar" #sidebar>
|
||||
<component
|
||||
:is="optionalRightSideBar"
|
||||
<template #sidebar>
|
||||
<RowEditModalSidebar
|
||||
:row="row"
|
||||
:table="table"
|
||||
:database="database"
|
||||
></component>
|
||||
></RowEditModalSidebar>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
@ -109,6 +108,7 @@ import modal from '@baserow/modules/core/mixins/modal'
|
|||
import CreateFieldContext from '@baserow/modules/database/components/field/CreateFieldContext'
|
||||
import RowEditModalFieldsList from './RowEditModalFieldsList.vue'
|
||||
import RowEditModalHiddenFieldsSection from './RowEditModalHiddenFieldsSection.vue'
|
||||
import RowEditModalSidebar from './RowEditModalSidebar.vue'
|
||||
import { getPrimaryOrFirstField } from '@baserow/modules/database/utils/field'
|
||||
|
||||
export default {
|
||||
|
@ -117,6 +117,7 @@ export default {
|
|||
CreateFieldContext,
|
||||
RowEditModalFieldsList,
|
||||
RowEditModalHiddenFieldsSection,
|
||||
RowEditModalSidebar,
|
||||
},
|
||||
mixins: [modal],
|
||||
props: {
|
||||
|
@ -165,11 +166,6 @@ export default {
|
|||
...mapGetters({
|
||||
navigationLoading: 'rowModalNavigation/getLoading',
|
||||
}),
|
||||
optionalRightSideBar() {
|
||||
return this.$registry
|
||||
.get('application', 'database')
|
||||
.getRowEditModalRightSidebarComponent(this.database, this.table)
|
||||
},
|
||||
modalRow() {
|
||||
return this.$store.getters['rowModal/get'](this._uid)
|
||||
},
|
||||
|
@ -209,6 +205,15 @@ export default {
|
|||
return null
|
||||
}
|
||||
},
|
||||
hasRightSidebar() {
|
||||
const allSidebarTypes = this.$registry.getOrderedList('rowModalSidebar')
|
||||
const activeSidebarTypes = allSidebarTypes.filter(
|
||||
(type) =>
|
||||
type.isDeactivated(this.database, this.table) === false &&
|
||||
type.getComponent()
|
||||
)
|
||||
return activeSidebarTypes.length > 0
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
<template>
|
||||
<Tabs
|
||||
:selected-index="selectedTabIndex"
|
||||
:full-height="true"
|
||||
:collapse-one-tab="true"
|
||||
:large="true"
|
||||
:full-width-header="true"
|
||||
class="row-edit-modal-sidebar"
|
||||
>
|
||||
<Tab
|
||||
v-for="sidebarType in sidebarTypes"
|
||||
:key="sidebarType.getType()"
|
||||
:title="sidebarType.getName()"
|
||||
>
|
||||
<component
|
||||
:is="sidebarType.getComponent()"
|
||||
:row="row"
|
||||
:table="table"
|
||||
:database="database"
|
||||
></component>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Tabs from '@baserow/modules/core/components/Tabs.vue'
|
||||
import Tab from '@baserow/modules/core/components/Tab.vue'
|
||||
|
||||
export default {
|
||||
name: 'RowEditModalSidebar',
|
||||
components: {
|
||||
Tabs,
|
||||
Tab,
|
||||
},
|
||||
props: {
|
||||
database: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
table: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
row: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
selectedTabIndex() {
|
||||
const types = this.sidebarTypes
|
||||
const index = types.findIndex((type) =>
|
||||
type.isSelectedByDefault(this.database, this.table)
|
||||
)
|
||||
return Math.max(index, 0)
|
||||
},
|
||||
sidebarTypes() {
|
||||
const allSidebarTypes = this.$registry.getOrderedList('rowModalSidebar')
|
||||
return allSidebarTypes.filter(
|
||||
(type) =>
|
||||
type.isDeactivated(this.database, this.table) === false &&
|
||||
type.getComponent()
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,68 @@
|
|||
<template>
|
||||
<div class="row-history-entry">
|
||||
<div class="row-history-entry__header">
|
||||
<span class="row_history-entry__initials">{{ initials }}</span>
|
||||
<span class="row-history-entry__name">{{ name }}</span>
|
||||
<span class="row-history-entry__timestamp" :title="timestampTooltip">{{
|
||||
formattedTimestamp
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="row-history-entry__content">
|
||||
<template v-for="field in entryFields">
|
||||
<div :key="field" class="row-history-entry__field">{{ field }}</div>
|
||||
<div :key="field + 'content'" class="row-history-entry__field-content">
|
||||
<div v-if="entry.before[field]">
|
||||
<div
|
||||
class="row-history-entry__diff row-history-entry__diff--removed"
|
||||
>
|
||||
{{ entry.before[field] }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="entry.after[field]">
|
||||
<div class="row-history-entry__diff row-history-entry__diff--added">
|
||||
{{ entry.after[field] }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from '@baserow/modules/core/moment'
|
||||
|
||||
export default {
|
||||
name: 'RowHistoryEntry',
|
||||
props: {
|
||||
entry: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
name() {
|
||||
return this.entry.user.name
|
||||
},
|
||||
initials() {
|
||||
return this.name.slice(0, 1).toUpperCase()
|
||||
},
|
||||
timestampTooltip() {
|
||||
return this.getLocalizedMoment(this.entry.timestamp).format('L LT')
|
||||
},
|
||||
formattedTimestamp() {
|
||||
return this.getLocalizedMoment(this.entry.timestamp).format('LT')
|
||||
},
|
||||
entryFields() {
|
||||
return new Set(
|
||||
Object.keys(this.entry.before).concat(Object.keys(this.entry.after))
|
||||
)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getLocalizedMoment(timestamp) {
|
||||
return moment.utc(timestamp).tz(moment.tz.guess())
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,89 @@
|
|||
<template>
|
||||
<div>
|
||||
<div v-if="!loaded && loading" class="loading-absolute-center" />
|
||||
<template v-else>
|
||||
<div class="row-history">
|
||||
<div v-if="totalCount > 0">
|
||||
<InfiniteScroll
|
||||
ref="infiniteScroll"
|
||||
:current-count="currentCount"
|
||||
:max-count="totalCount"
|
||||
:loading="loading"
|
||||
:reverse="true"
|
||||
:render-end="false"
|
||||
>
|
||||
<template #default>
|
||||
<RowHistoryEntry
|
||||
v-for="entry in entries"
|
||||
:key="entry.id"
|
||||
:entry="entry"
|
||||
>
|
||||
</RowHistoryEntry>
|
||||
</template>
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
<div v-else class="row-history__empty">
|
||||
<i class="row-history__empty-icon fas fa-history"></i>
|
||||
<div class="row-history__empty-text">
|
||||
{{ $t('rowHistorySidebar.empty') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import { notifyIf } from '@baserow/modules/core/utils/error'
|
||||
import InfiniteScroll from '@baserow/modules/core/components/helpers/InfiniteScroll'
|
||||
import RowHistoryEntry from '@baserow/modules/database/components/row/RowHistoryEntry.vue'
|
||||
|
||||
export default {
|
||||
name: 'RowHistorySidebar',
|
||||
components: {
|
||||
InfiniteScroll,
|
||||
RowHistoryEntry,
|
||||
},
|
||||
props: {
|
||||
database: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
table: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
row: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
entries: 'rowHistory/getSortedEntries',
|
||||
loading: 'rowHistory/getLoading',
|
||||
loaded: 'rowHistory/getLoaded',
|
||||
currentCount: 'rowHistory/getCurrentCount',
|
||||
totalCount: 'rowHistory/getTotalCount',
|
||||
}),
|
||||
},
|
||||
async created() {
|
||||
await this.initialLoad()
|
||||
},
|
||||
methods: {
|
||||
async initialLoad() {
|
||||
try {
|
||||
const tableId = this.table.id
|
||||
const rowId = this.row.id
|
||||
await this.$store.dispatch('rowHistory/fetchInitial', {
|
||||
tableId,
|
||||
rowId,
|
||||
})
|
||||
} catch (e) {
|
||||
notifyIf(e, 'application')
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -773,5 +773,9 @@
|
|||
},
|
||||
"collaboratorAddedToRowNotification": {
|
||||
"title": "{sender} assigned you to {fieldName} in row {rowId} in {tableName}"
|
||||
},
|
||||
"rowHistorySidebar": {
|
||||
"name": "History",
|
||||
"empty": "No changes yet. You'll be able to track any changes to this row here."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -103,6 +103,7 @@ import formStore from '@baserow/modules/database/store/view/form'
|
|||
import rowModal from '@baserow/modules/database/store/rowModal'
|
||||
import publicStore from '@baserow/modules/database/store/view/public'
|
||||
import rowModalNavigationStore from '@baserow/modules/database/store/rowModalNavigation'
|
||||
import rowHistoryStore from '@baserow/modules/database/store/rowHistory'
|
||||
|
||||
import { registerRealtimeEvents } from '@baserow/modules/database/realtime'
|
||||
import { CSVTableExporterType } from '@baserow/modules/database/exporterTypes'
|
||||
|
@ -229,6 +230,7 @@ import { FormViewFormModeType } from '@baserow/modules/database/formViewModeType
|
|||
import { CollaborativeViewOwnershipType } from '@baserow/modules/database/viewOwnershipTypes'
|
||||
import { DatabasePlugin } from '@baserow/modules/database/plugins'
|
||||
import { CollaboratorAddedToRowNotificationType } from '@baserow/modules/database/notificationTypes'
|
||||
import { HistoryRowModalSidebarType } from '@baserow/modules/database/rowModalSidebarTypes'
|
||||
|
||||
import en from '@baserow/modules/database/locales/en.json'
|
||||
import fr from '@baserow/modules/database/locales/fr.json'
|
||||
|
@ -258,6 +260,7 @@ export default (context) => {
|
|||
store.registerModule('field', fieldStore)
|
||||
store.registerModule('rowModal', rowModal)
|
||||
store.registerModule('rowModalNavigation', rowModalNavigationStore)
|
||||
store.registerModule('rowHistory', rowHistoryStore)
|
||||
store.registerModule('page/view/grid', gridStore)
|
||||
store.registerModule('page/view/gallery', galleryStore)
|
||||
store.registerModule('page/view/form', formStore)
|
||||
|
@ -646,5 +649,10 @@ export default (context) => {
|
|||
new CollaboratorAddedToRowNotificationType(context)
|
||||
)
|
||||
|
||||
app.$registry.register(
|
||||
'rowModalSidebar',
|
||||
new HistoryRowModalSidebarType(context)
|
||||
)
|
||||
|
||||
registerRealtimeEvents(app.$realtime)
|
||||
}
|
||||
|
|
76
web-frontend/modules/database/rowModalSidebarTypes.js
Normal file
76
web-frontend/modules/database/rowModalSidebarTypes.js
Normal file
|
@ -0,0 +1,76 @@
|
|||
import { Registerable } from '@baserow/modules/core/registry'
|
||||
import RowHistorySidebar from '@baserow/modules/database/components/row/RowHistorySidebar.vue'
|
||||
import {
|
||||
featureFlagIsEnabled,
|
||||
getFeatureFlags,
|
||||
} from '@baserow/modules/core/utils/env'
|
||||
|
||||
export class RowModalSidebarType extends Registerable {
|
||||
/**
|
||||
* A human readable name
|
||||
*/
|
||||
getName() {
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* The component to render in the sidebar
|
||||
*/
|
||||
getComponent() {
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the sidebar component should be shown
|
||||
*/
|
||||
isDeactivated(database, table) {
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* When true, the sidebar type indicates
|
||||
* that it should be focused first.
|
||||
*/
|
||||
isSelectedByDefault(database) {
|
||||
return false
|
||||
}
|
||||
|
||||
getOrder() {
|
||||
return 50
|
||||
}
|
||||
}
|
||||
|
||||
export class HistoryRowModalSidebarType extends RowModalSidebarType {
|
||||
static getType() {
|
||||
return 'history'
|
||||
}
|
||||
|
||||
getName() {
|
||||
return this.app.i18n.t('rowHistorySidebar.name')
|
||||
}
|
||||
|
||||
getComponent() {
|
||||
return RowHistorySidebar
|
||||
}
|
||||
|
||||
isDeactivated(database, table) {
|
||||
const featureFlags = getFeatureFlags(this.app.$config)
|
||||
const featureFlagEnabled = featureFlagIsEnabled(featureFlags, 'row_history')
|
||||
return !featureFlagEnabled
|
||||
|
||||
// TODO:
|
||||
// return this.app.$hasPermission(
|
||||
// 'database.table.read_row_history',
|
||||
// table,
|
||||
// database.workspace.id
|
||||
// )
|
||||
}
|
||||
|
||||
isSelectedByDefault(database) {
|
||||
return true
|
||||
}
|
||||
|
||||
getOrder() {
|
||||
return 10
|
||||
}
|
||||
}
|
47
web-frontend/modules/database/services/rowHistory.js
Normal file
47
web-frontend/modules/database/services/rowHistory.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
export default (client) => {
|
||||
return {
|
||||
fetchAll({ tableId, rowId }) {
|
||||
// mocked for now
|
||||
return {
|
||||
data: {
|
||||
count: 2,
|
||||
next: null,
|
||||
previous: null,
|
||||
results: [
|
||||
{
|
||||
id: 1,
|
||||
action_type: 'update_row',
|
||||
user: {
|
||||
id: 1,
|
||||
name: 'John Wick',
|
||||
},
|
||||
timestamp: '2023-08-09T00:30:00Z',
|
||||
before: {
|
||||
field_1: 'a',
|
||||
},
|
||||
after: {
|
||||
field_1: 'aa',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
action_type: 'update_row',
|
||||
user: {
|
||||
id: 2,
|
||||
name: 'Paul Smith',
|
||||
},
|
||||
timestamp: '2023-08-09T00:30:00Z',
|
||||
before: {
|
||||
field_2: 'a',
|
||||
},
|
||||
after: {
|
||||
field_1: 'aa',
|
||||
field_2: 'a',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
79
web-frontend/modules/database/store/rowHistory.js
Normal file
79
web-frontend/modules/database/store/rowHistory.js
Normal file
|
@ -0,0 +1,79 @@
|
|||
import RowHistoryService from '@baserow/modules/database/services/rowHistory'
|
||||
|
||||
export const state = () => ({
|
||||
entries: [],
|
||||
loading: false,
|
||||
loaded: false,
|
||||
totalCount: 0,
|
||||
loadedRowId: false,
|
||||
loadedTableId: false,
|
||||
})
|
||||
|
||||
export const mutations = {
|
||||
ADD_ENTRIES(state, { entries }) {
|
||||
state.entries = entries
|
||||
},
|
||||
RESET_ENTRIES(state) {
|
||||
state.entries = []
|
||||
state.totalCount = 0
|
||||
},
|
||||
SET_LOADING(state, loading) {
|
||||
state.loading = loading
|
||||
},
|
||||
SET_LOADED(state, loaded) {
|
||||
state.loaded = loaded
|
||||
},
|
||||
SET_LOADED_TABLE_AND_ROW(state, { tableId, rowId }) {
|
||||
state.loadedRowId = rowId
|
||||
state.loadedTableId = tableId
|
||||
},
|
||||
SET_TOTAL_COUNT(state, totalCount) {
|
||||
state.totalCount = totalCount
|
||||
},
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
async fetchInitial({ commit }, { tableId, rowId }) {
|
||||
commit('RESET_ENTRIES')
|
||||
commit('SET_LOADING', true)
|
||||
commit('SET_LOADED', false)
|
||||
try {
|
||||
const { data } = await RowHistoryService(this.$client).fetchAll(
|
||||
tableId,
|
||||
rowId
|
||||
)
|
||||
commit('ADD_ENTRIES', { entries: data.results })
|
||||
commit('SET_TOTAL_COUNT', data.count)
|
||||
commit('SET_LOADED_TABLE_AND_ROW', { tableId, rowId })
|
||||
commit('SET_LOADED', true)
|
||||
} finally {
|
||||
commit('SET_LOADING', false)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export const getters = {
|
||||
getSortedEntries(state) {
|
||||
return state.entries
|
||||
},
|
||||
getCurrentCount(state) {
|
||||
return state.entries.length
|
||||
},
|
||||
getTotalCount(state) {
|
||||
return state.totalCount
|
||||
},
|
||||
getLoading(state) {
|
||||
return state.loading
|
||||
},
|
||||
getLoaded(state) {
|
||||
return state.loaded
|
||||
},
|
||||
}
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
mutations,
|
||||
}
|
Loading…
Add table
Reference in a new issue