Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -57,22 +57,22 @@
</template>

<script>
import { mapState } from 'vuex'

import modal from '@baserow/modules/core/mixins/modal'
import error from '@baserow/modules/core/mixins/error'
import job from '@baserow/modules/core/mixins/job'
import moment from '@baserow/modules/core/moment'
import { getHumanPeriodAgoCount } from '@baserow/modules/core/utils/date'
import ExportLoadingBar from '@baserow/modules/database/components/export/ExportLoadingBar'
import AuditLogExportForm from '@baserow_enterprise/components/admin/forms/AuditLogExportForm'
import AuditLogAdminService from '@baserow_enterprise/services/auditLog'
import { AuditLogExportJobType } from '@baserow_enterprise/jobTypes'

const MAX_EXPORT_FILES = 4

export default {
name: 'AuditLogExportModal',
components: { AuditLogExportForm, ExportLoadingBar },
mixins: [modal, error],
mixins: [modal, error, job],
props: {
filters: {
type: Object,
Expand All @@ -86,9 +86,6 @@ export default {
data() {
return {
loading: false,
timeoutId: null,
timeNextPoll: 1000,
job: null,
lastFinishedJobs: [],
}
},
Expand All @@ -103,35 +100,30 @@ export default {
this.lastFinishedJobs = filteredJobs.filter(
(job) => job.state === 'finished'
)
const runningJob = filteredJobs.find(
(job) => !['failed', 'cancelled', 'finished'].includes(job.state)
)
this.job = runningJob || null
if (this.job) {
this.scheduleNextPoll()
} else {
this.loadRunningJob()
if (!this.jobIsRunning) {
this.loading = false
}
},
// the poll timeout can only be scheduled on the client
fetchOnServer: false,
computed: {
jobHasFailed() {
return ['failed', 'cancelled'].includes(this.job.state)
},
jobIsRunning() {
return (
this.job !== null && this.job.state !== 'finished' && !this.jobHasFailed
methods: {
loadRunningJob() {
const runningJob = this.$store.getters['job/getUnfinishedJobs'].find(
(job) => {
if (job.type !== AuditLogExportJobType.getType()) {
return false
}
if (this.workspaceId) {
return job.filter_workspace_id === this.workspaceId
}
return true
}
)
if (runningJob) {
this.job = runningJob
this.loading = true
}
},
...mapState({
selectedTableViews: (state) => state.view.items,
}),
},
beforeUnmount() {
this.stopPollIfRunning()
},
methods: {
getExportedFilename(job) {
return job ? `audit_log_${job.created_on}.csv` : ''
},
Expand All @@ -152,7 +144,6 @@ export default {
return this.$t(`datetime.${period}Ago`, { count }, count)
},
hidden() {
this.stopPollIfRunning()
if (this.job && !this.jobIsRunning) {
this.lastFinishedJobs = [this.job, ...this.lastFinishedJobs]
this.job = null
Expand All @@ -165,7 +156,6 @@ export default {

this.loading = true
this.hideError()
this.stopPollIfRunning()
const filters = Object.fromEntries(
Object.entries(this.filters).map(([key, value]) => [
`filter_${key}`,
Expand All @@ -188,49 +178,28 @@ export default {
0,
MAX_EXPORT_FILES - 1
)
this.job = data
this.scheduleNextPoll()
await this.createAndMonitorJob(data)
} catch (error) {
this.loading = false
this.handleError(error, 'export')
}
},
async getJobInfo() {
try {
const { data } = await AuditLogAdminService(
this.$client
).getExportJobInfo(this.job.id)
this.job = data

if (this.jobIsRunning) {
this.scheduleNextPoll()
return
}

this.loading = false
if (this.jobHasFailed) {
let title, message
if (this.job.status === 'failed') {
title = this.$t('auditLogExportModal.failedTitle')
message = this.$t('auditLogExportModal.failedDescription')
} else {
// cancelled
title = this.$t('auditLogExportModal.cancelledTitle')
message = this.$t('auditLogExportModal.cancelledDescription')
}
this.showError(title, message)
}
} catch (error) {
this.handleError(error, 'export')
}
onJobFinished() {
this.loading = false
},
scheduleNextPoll() {
this.timeNextPoll = Math.min(this.timeNextPoll * 1.1, 5000)
this.timeoutId = setTimeout(this.getJobInfo, this.timeNextPoll)
onJobFailed() {
this.loading = false
this.showError(
this.$t('auditLogExportModal.failedTitle'),
this.$t('auditLogExportModal.failedDescription')
)
},
stopPollIfRunning() {
clearTimeout(this.timeoutId)
this.timeoutId = null
onJobCancelled() {
this.loading = false
this.showError(
this.$t('auditLogExportModal.cancelledTitle'),
this.$t('auditLogExportModal.cancelledDescription')
)
},
valuesChanged() {
this.isValid = this.$refs.form.isFormValid()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export class RowCommentMentionNotificationType extends NotificationType {
params: {
databaseId: notificationData.database_id,
tableId: notificationData.table_id,
viewId: '',
rowId: notificationData.row_id,
},
}
Expand All @@ -47,6 +48,7 @@ export class RowCommentNotificationType extends NotificationType {
params: {
databaseId: notificationData.database_id,
tableId: notificationData.table_id,
viewId: '',
rowId: notificationData.row_id,
},
}
Expand Down
1 change: 0 additions & 1 deletion web-frontend/config/nuxt.config.base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ function baserowModuleConfig(
enterpriseBase + '/modules/baserow_enterprise/module.js'
)
}
// baseModules.push('@nuxtjs/sentry')

const modules = baseModules.concat(additionalModules)

Expand Down
6 changes: 2 additions & 4 deletions web-frontend/env-remap.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,8 @@ const envMapping = {
BASEROW_PRICING_URL: 'NUXT_PUBLIC_BASEROW_PRICING_URL',
BASEROW_ENTERPRISE_ASSISTANT_LLM_MODEL:
'NUXT_PUBLIC_BASEROW_ENTERPRISE_ASSISTANT_LLM_MODEL',

// Additional env vars
SENTRY_DSN: 'NUXT_PUBLIC_SENTRY_CONFIG_DSN',
SENTRY_ENVIRONMENT: 'NUXT_PUBLIC_SENTRY_CONFIG_ENVIRONMENT',
SENTRY_DSN: 'NUXT_PUBLIC_SENTRY_DSN',
SENTRY_ENVIRONMENT: 'NUXT_PUBLIC_SENTRY_ENVIRONMENT',
MEDIA_URL: 'NUXT_PUBLIC_MEDIA_URL',
}

Expand Down
10 changes: 4 additions & 6 deletions web-frontend/modules/core/module.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,8 @@ export default defineNuxtModule({
baserowDisableSupport: '',
baserowIntegrationsPeriodicMinuteMin: '1',
mediaUrl: 'http://localhost:4000/media/',
sentry: {
config: {
dsn: '',
environment: '',
},
},
sentryDsn: '',
sentryEnvironment: '',
}
)

Expand Down Expand Up @@ -132,6 +128,8 @@ export default defineNuxtModule({
addPlugin(resolve('plugins/featureFlags.js'))
addPlugin(resolve('plugins/papa.js'))
addPlugin(resolve('plugins/ensureRender.js'))
addPlugin(resolve('plugins/sentry.client.js'))
addPlugin(resolve('plugins/sentry.server.js'))
addPlugin(resolve('plugins/version.js'))
addPlugin(resolve('plugins/posthog.js'))
addPlugin(resolve('plugins/vueDatepicker.js'))
Expand Down
33 changes: 33 additions & 0 deletions web-frontend/modules/core/plugins/sentry.client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
export default defineNuxtPlugin(async (nuxtApp) => {
// Only run on client side
if (import.meta.server) {
return
}

const config = useRuntimeConfig()
const dsn = config.public.sentryDsn

if (!dsn || dsn === '') {
return
}

const Sentry = await import('@sentry/vue')

Sentry.init({
app: nuxtApp.vueApp,
dsn,
environment: config.public.sentryEnvironment || 'production',
integrations: [
Sentry.browserTracingIntegration({
router: nuxtApp.$router,
}),
Sentry.replayIntegration({
maskAllText: true,
blockAllMedia: true,
}),
],
tracesSampleRate: 1.0,
replaysSessionSampleRate: 0,
replaysOnErrorSampleRate: 1.0,
})
})
35 changes: 35 additions & 0 deletions web-frontend/modules/core/plugins/sentry.server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
export default defineNuxtPlugin(async (nuxtApp) => {
if (import.meta.client) {
return
}

const config = useRuntimeConfig()
const dsn = config.public.sentryDsn

if (!dsn || dsn === '') {
return
}

const Sentry = await import('@sentry/node')

Sentry.init({
dsn,
environment: config.public.sentryEnvironment || 'production',
tracesSampleRate: 1.0,
})

nuxtApp.hook('app:error', (error) => {
Sentry.captureException(error)
})

nuxtApp.hook('vue:error', (error, instance, info) => {
Sentry.captureException(error, {
contexts: {
vue: {
componentName: instance?.$options?.name,
errorInfo: info,
},
},
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export default {
return {
databaseId: this.notification.data.database_id,
tableId: this.notification.data.table_id,
viewId: '',
rowId: this.notification.data.row_id,
}
},
Expand Down
36 changes: 21 additions & 15 deletions web-frontend/modules/database/components/row/SelectRowContent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -233,20 +233,24 @@ export default {
return
}

// Remove the not existing keys because the related fields might have been
// deleted in the meantime, and so we're keeping the local storage clean.
// Remove entries for fields that no longer exist in the table, keeping
// IndexedDB clean. Uses allFields (the actual table fields) instead of
// fieldOptions (grid view API response) which may be empty or incomplete.
const existingFieldIds = new Set(
this.allFields.map((field) => field.id.toString())
)
value = Object.fromEntries(
Object.entries(value).filter((key) => {
return Object.prototype.hasOwnProperty.call(this.fieldOptions, key[0])
})
Object.entries(value).filter(([key]) => existingFieldIds.has(key))
)

try {
// clone() strips Vue 3 Proxy wrappers so the value can be stored via
// IndexedDB's structured clone algorithm.
await setData(
databaseName,
storeName,
this.persistentFieldOptionsKey,
value
clone(value)
)
} catch (error) {
/* empty */
Expand All @@ -267,6 +271,17 @@ export default {

await this.orderFieldsByFirstGridViewFieldOptions(this.tableId)

if (this.persistentFieldOptionsKey) {
try {
const override = await getData(
databaseName,
storeName,
this.persistentFieldOptionsKey
)
this.fieldOptionsOverride = override || {}
} catch (error) {}
}

// Because the page data depends on having some initial metadata we mark the state
// as loaded after that. Only a loading animation is shown if there isn't any
// data.
Expand Down Expand Up @@ -338,15 +353,6 @@ export default {
data: { field_options: fieldOptions },
} = await ViewService(this.$client).fetchFieldOptions(views[0].id)
this.fieldOptions = fieldOptions

if (this.persistentFieldOptionsKey) {
const override = await getData(
databaseName,
storeName,
this.persistentFieldOptionsKey
)
this.fieldOptionsOverride = override || {}
}
} catch (error) {
notifyIf(error, 'view')
}
Expand Down
3 changes: 3 additions & 0 deletions web-frontend/modules/database/notificationTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export class CollaboratorAddedToRowNotificationType extends NotificationType {
params: {
databaseId: notificationData.database_id,
tableId: notificationData.table_id,
viewId: '',
rowId: notificationData.row_id,
},
}
Expand All @@ -50,6 +51,7 @@ export class FormSubmittedNotificationType extends NotificationType {
params: {
databaseId: notificationData.database_id,
tableId: notificationData.table_id,
viewId: '',
rowId: notificationData.row_id,
},
}
Expand All @@ -75,6 +77,7 @@ export class UserMentionInRichTextFieldNotificationType extends NotificationType
params: {
databaseId: notificationData.database_id,
tableId: notificationData.table_id,
viewId: '',
rowId: notificationData.row_id,
},
}
Expand Down
Loading
Loading