1
0
Fork 0
mirror of https://gitlab.com/bramw/baserow.git synced 2025-05-11 20:05:34 +00:00

Add automation settings modal

This commit is contained in:
Jonathan Adeline 2025-04-11 15:56:36 +04:00
parent 6f756b89aa
commit 6963d0d42f
12 changed files with 575 additions and 6 deletions

View file

@ -35,7 +35,11 @@ export default function (
const modules = baseModules.concat(additionalModules)
return {
modules,
buildModules: ['@nuxtjs/stylelint-module', '@nuxtjs/svg'],
buildModules: [
'@nuxtjs/stylelint-module',
'@nuxtjs/svg',
'@nuxtjs/composition-api/module',
],
sentry: {
clientIntegrations: {
Dedupe: {},

View file

@ -0,0 +1,69 @@
import { Registerable } from '@baserow/modules/core/registry'
import GeneralSettings from '@baserow/modules/automation/components/settings/GeneralSettings'
import IntegrationSettings from '@baserow/modules/automation/components/settings/IntegrationSettings'
class AutomationSettingType extends Registerable {
static getType() {
return null
}
get name() {
return null
}
get icon() {
return null
}
get component() {
return null
}
get componentPadding() {
return true
}
}
export class GeneralAutomationSettingsType extends AutomationSettingType {
static getType() {
return 'general'
}
get name() {
return this.app.i18n.t('builderSettingTypes.generalName')
}
get icon() {
return 'iconoir-settings'
}
getOrder() {
return 1
}
get component() {
return GeneralSettings
}
}
export class IntegrationsAutomationSettingsType extends AutomationSettingType {
static getType() {
return 'integrations'
}
get name() {
return this.app.i18n.t('builderSettingTypes.integrationsName')
}
get icon() {
return 'iconoir-ev-plug'
}
getOrder() {
return 10
}
get component() {
return IntegrationSettings
}
}

View file

@ -5,18 +5,42 @@
:application="application"
:workspace="workspace"
>
<template #additional-context-items></template>
<template #additional-context-items>
<li
v-if="
$hasPermission(
'application.update',
application,
application.workspace.id
)
"
class="context__menu-item"
>
<a class="context__menu-item-link" @click="openSettingsModal">
<i class="context__menu-item-icon iconoir-settings"></i>
{{ $t('sidebarComponentBuilder.settings') }}
</a>
</li>
</template>
</ApplicationContext>
<AutomationSettingsModal
ref="automationSettingsModal"
:automation="application"
/>
</div>
</template>
<script>
import { defineComponent, ref } from 'vue'
import AutomationSettingsModal from '@baserow/modules/automation/components/settings/AutomationSettingsModal'
import ApplicationContext from '@baserow/modules/core/components/application/ApplicationContext'
import applicationContext from '@baserow/modules/core/mixins/applicationContext'
export default {
export default defineComponent({
components: {
ApplicationContext,
AutomationSettingsModal,
},
mixins: [applicationContext],
props: {
@ -29,5 +53,20 @@ export default {
required: true,
},
},
}
setup() {
const context = ref(null)
const automationSettingsModal = ref(null)
const openSettingsModal = () => {
automationSettingsModal.value.show()
context.value.hide()
}
return {
context,
automationSettingsModal,
openSettingsModal,
}
},
})
</script>

View file

@ -0,0 +1,137 @@
<template>
<Modal
left-sidebar
left-sidebar-scrollable
:content-padding="
settingSelected == null ? true : settingSelected.componentPadding
"
>
<template #sidebar>
<div class="modal-sidebar__head">
<div class="modal-sidebar__head-name">
{{ $t('automationSettingsModal.title') }}
</div>
</div>
<ul class="modal-sidebar__nav">
<li v-for="setting in registeredSettings" :key="setting.getType()">
<a
class="modal-sidebar__nav-link"
:class="{ active: setting === settingSelected }"
@click="settingSelected = setting"
>
<i class="modal-sidebar__nav-icon" :class="setting.icon"></i>
{{ setting.name }}
</a>
</li>
</ul>
</template>
<template v-if="settingSelected" #content>
<component
:is="settingSelected.component"
ref="settingSelected"
:automation="automation"
:default-values="automation"
:hide-after-create="hideAfterCreate"
:force-display-form="displaySelectedSettingForm"
@hide-modal="emitCreatedRecord($event)"
></component>
</template>
</Modal>
</template>
<script>
import modal from '@baserow/modules/core/mixins/modal'
import { defineComponent, ref, computed, watch, getCurrentInstance } from 'vue'
import { useContext } from '@nuxtjs/composition-api'
export default defineComponent({
name: 'AutomationSettingsModal',
mixins: [modal],
props: {
automation: {
type: Object,
required: true,
},
/**
* If you want the selected setting form to hide the builder settings modal
* after a record is created, set this to `true`.
*/
hideAfterCreate: {
type: Boolean,
required: false,
default: false,
},
},
setup(props, { emit }) {
const instance = getCurrentInstance()
const { app } = useContext()
const settingSelected = ref(null)
const displaySelectedSettingForm = ref(false)
const registeredSettings = computed(() => {
return app.$registry.getOrderedList('automationSettings')
})
// Watch for changes in the selected setting
watch(settingSelected, (newSetting, oldSetting) => {
if (
oldSetting &&
newSetting !== oldSetting &&
displaySelectedSettingForm.value
) {
displaySelectedSettingForm.value = false
}
})
// Method to emit the created record and hide the modal
const emitCreatedRecord = (createdRecordId) => {
instance.proxy.hide()
emit('created', createdRecordId)
}
return {
registeredSettings,
emitCreatedRecord,
}
},
data() {
return {
settingSelected: null,
displaySelectedSettingForm: false,
}
},
methods: {
/**
* Override show method from the modal mixin to handle setting selection.
*/
show(
selectSettingType = null,
displaySelectedSettingFormValue = false,
...args
) {
// If we've been instructed to show a specific setting component,
// then ensure it's displayed first.
if (selectSettingType) {
this.settingSelected = this.$registry.get(
'automationSettings',
selectSettingType
)
}
// If no `selectSettingType` was provided then choose the first setting.
if (!this.settingSelected) {
this.settingSelected = this.registeredSettings[0]
}
// If we've been instructed to show the modal, and make the
// selected setting component's form display, then do so.
this.displaySelectedSettingForm = displaySelectedSettingFormValue
// Call the original show method from the mixin
return modal.methods.show.call(this, ...args)
},
},
})
</script>

View file

@ -0,0 +1,125 @@
<template>
<div>
<h2 class="box__title">{{ $t('generalSettings.titleOverview') }}</h2>
<FormGroup
small-label
:label="$t('generalSettings.nameLabel')"
:error="fieldHasErrors('name')"
required
class="margin-bottom-2"
>
<FormInput
v-model="v$.values.name.$model"
:error="fieldHasErrors('name')"
size="large"
></FormInput>
<template #error>
{{ v$.values.name.$errors[0].$message }}
</template>
</FormGroup>
<div class="separator"></div>
<FormGroup
small-label
:label="$t('generalSettings.notificationLabel')"
:error="fieldHasErrors('notification')"
required
class="margin-bottom-2"
>
<Checkbox v-model="v$.values.notification.$model" class="margin-top-1">{{
$t('generalSettings.notificationCheckboxLabel')
}}</Checkbox>
</FormGroup>
</div>
</template>
<script>
import { useVuelidate } from '@vuelidate/core'
import {
reactive,
getCurrentInstance,
defineComponent,
toRefs,
watch,
} from 'vue'
import { useStore, useContext } from '@nuxtjs/composition-api'
import { required, helpers } from '@vuelidate/validators'
import form from '@baserow/modules/core/mixins/form'
import _ from 'lodash'
export default defineComponent({
name: 'GeneralSettings',
mixins: [form],
props: {
automation: {
type: Object,
required: true,
},
},
setup(props) {
const instance = getCurrentInstance()
const { app } = useContext()
const i18n = app.i18n
const store = useStore()
const { automation } = toRefs(props)
const values = reactive({
values: {
name: '',
notification: false,
},
})
const rules = {
values: {
name: {
required: helpers.withMessage(
i18n.t('error.requiredField'),
required
),
},
notification: {},
},
}
const v$ = useVuelidate(rules, values, { $lazy: true })
const updateAutomation = async (updatedValues) => {
if (_.isMatch(automation.value, updatedValues)) {
return
}
try {
await store.dispatch('application/update', {
automation: automation.value,
values: updatedValues,
})
} catch (error) {
const title = i18n.t('generalSettings.cantUpdateAutomationTitle')
const message = i18n.t(
'generalSettings.cantUpdateAutomationDescription'
)
store.dispatch('toast/error', { title, message })
instance.proxy.reset()
}
}
watch(
() => values.values,
(newValues) => {
updateAutomation(newValues)
},
{ deep: true }
)
return {
values: values.values,
v$,
updateAutomation,
}
},
})
</script>

View file

@ -0,0 +1,121 @@
<template>
<div>
<h2 class="box__title">{{ $t('integrationSettings.title') }}</h2>
<div v-if="state === 'pending'" class="integration-settings__loader" />
<template v-if="state === 'loaded'">
<template v-if="integrations && integrations.length > 0">
<p class="margin-top-3">
{{ $t('integrationSettings.integrationMessage') }}
</p>
<div
v-for="integration in integrations"
:key="integration.id"
class="integration-settings__integration"
>
<Presentation
:image="getIntegrationType(integration).image"
:title="integration.name"
:subtitle="getIntegrationType(integration).getSummary(integration)"
:rounded-icon="false"
avatar-color="transparent"
style="flex: 1"
/>
<div class="integration-settings__integration-actions">
<ButtonIcon
icon="iconoir-edit"
@click="
$refs[`IntegrationCreateEditModal_${integration.id}`][0].show()
"
/>
<ButtonIcon
icon="iconoir-bin"
@click="deleteIntegration(integration)"
/>
</div>
<IntegrationCreateEditModal
:ref="`IntegrationCreateEditModal_${integration.id}`"
:data-integration-id="integration.id"
:application="automation"
:integration="integration"
/>
</div>
</template>
<p v-else class="margin-top-3">
{{ $t('integrationSettings.noIntegrationMessage') }}
</p>
</template>
</div>
</template>
<script>
import IntegrationCreateEditModal from '@baserow/modules/core/components/integrations/IntegrationCreateEditModal'
import { notifyIf } from '@baserow/modules/core/utils/error'
import { defineComponent, ref, computed, onMounted, toRefs } from 'vue'
import { useStore, useContext } from '@nuxtjs/composition-api'
export default defineComponent({
name: 'IntegrationSettings',
components: { IntegrationCreateEditModal },
props: {
automation: {
type: Object,
required: true,
},
},
setup(props) {
const { automation } = toRefs(props)
const store = useStore()
const { app } = useContext()
const state = ref('loaded')
const integrationTypes = computed(() => {
return app.$registry.getOrderedList('integration')
})
const integrations = computed(() => {
return store.getters['integration/getIntegrations'](automation.value)
})
const getIntegrationType = (integration) => {
return app.$registry.get('integration', integration.type)
}
const fetchIntegrations = async () => {
try {
state.value = 'pending'
await store.dispatch('integration/fetch', {
application: automation.value,
})
state.value = 'loaded'
} catch (error) {
notifyIf(error)
state.value = 'loaded'
}
}
const deleteIntegration = async (integration) => {
try {
await store.dispatch('integration/delete', {
application: automation.value,
integrationId: integration.id,
})
} catch (error) {
notifyIf(error)
}
}
onMounted(async () => {
await fetchIntegrations()
})
return {
state,
integrationTypes,
integrations,
getIntegrationType,
deleteIntegration,
}
},
})
</script>

View file

@ -22,5 +22,18 @@
},
"trashType": {
"workflow": "workflow"
},
"generalSettings": {
"titleOverview": "General",
"nameLabel": "Automation name",
"notificationLabel": "Notifications",
"notificationCheckboxLabel": "Get notified when this automation fails",
"cantUpdateAutomationTitle": "Couldn't Update Automation",
"cantUpdateAutomationDescription": "Sorry, could not update the automation."
},
"integrationSettings": {
"title": "Integrations",
"noIntegrationMessage": "You have not yet created any integrations. They can be created by adding data source, action or user authentication.",
"integrationMessage": "You can create new integrations by adding data source, action or user authentication."
}
}

View file

@ -6,6 +6,10 @@ import es from '@baserow/modules/automation/locales/es.json'
import it from '@baserow/modules/automation/locales/it.json'
import pl from '@baserow/modules/automation/locales/pl.json'
import ko from '@baserow/modules/automation/locales/ko.json'
import {
GeneralAutomationSettingsType,
IntegrationsAutomationSettingsType,
} from '@baserow/modules/automation/automationSettingTypes'
import { registerRealtimeEvents } from '@baserow/modules/automation/realtime'
import { AutomationApplicationType } from '@baserow/modules/automation/applicationTypes'
@ -48,5 +52,15 @@ export default (context) => {
'job',
new DuplicateAutomationWorkflowJobType(context)
)
app.$registry.registerNamespace('automationSettings')
app.$registry.register(
'automationSettings',
new GeneralAutomationSettingsType(context)
)
app.$registry.register(
'automationSettings',
new IntegrationsAutomationSettingsType(context)
)
}
}

View file

@ -1017,5 +1017,8 @@
"smile": "Smile",
"thumbsUp": "Thumbs Up",
"flag": "Flag"
},
"automationSettingsModal": {
"title": "Workflow"
}
}

View file

@ -1,4 +1,4 @@
.separator {
margin: 30px 0;
border-bottom: solid 1px $color-neutral-200;
border-bottom: solid 1px $palette-neutral-200;
}

View file

@ -29,6 +29,7 @@
"test-storybook": "test-storybook"
},
"dependencies": {
"@nuxtjs/composition-api": "^0.34.0",
"@nuxtjs/i18n": "7.3.1",
"@nuxtjs/sentry": "7.5.0",
"@storybook/core-client": "6.5.9",

View file

@ -2256,6 +2256,11 @@
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
"@jridgewell/sourcemap-codec@^1.5.0":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a"
integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==
"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.13", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.9":
version "0.3.20"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz#72e45707cf240fa6b081d0366f8265b0cd10197f"
@ -2720,6 +2725,18 @@
webpack-node-externals "^3.0.0"
webpackbar "^5.0.2"
"@nuxtjs/composition-api@^0.34.0":
version "0.34.0"
resolved "https://registry.yarnpkg.com/@nuxtjs/composition-api/-/composition-api-0.34.0.tgz#4608b712164958c0efdbd3e8195137a2d6243391"
integrity sha512-2BWv4zmlFhu09eo8oeXk11jVdsRLh6vXwtaDADMaZ8Do8SoY2fEk3dmtAkr3aUQuSIzOD/jdWBnd0h99/YIrSA==
dependencies:
defu "^6.1.4"
estree-walker "^2.0.2"
fs-extra "^11.2.0"
magic-string "^0.30.9"
pathe "^1.1.2"
ufo "^1.5.3"
"@nuxtjs/eslint-config@12.0.0":
version "12.0.0"
resolved "https://registry.yarnpkg.com/@nuxtjs/eslint-config/-/eslint-config-12.0.0.tgz#36ac450efd20e3efb8f8a961e094b267c49adac4"
@ -8211,6 +8228,11 @@ defu@^6.0.0, defu@^6.1.2, defu@^6.1.3:
resolved "https://registry.yarnpkg.com/defu/-/defu-6.1.3.tgz#6d7f56bc61668e844f9f593ace66fd67ef1205fd"
integrity sha512-Vy2wmG3NTkmHNg/kzpuvHhkqeIx3ODWqasgCRbKtbXEN0G+HpEEv9BtJLp7ZG1CZloFaC41Ah3ZFbq7aqCqMeQ==
defu@^6.1.4:
version "6.1.4"
resolved "https://registry.yarnpkg.com/defu/-/defu-6.1.4.tgz#4e0c9cf9ff68fe5f3d7f2765cc1a012dfdcb0479"
integrity sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==
del@^6.0.0:
version "6.1.1"
resolved "https://registry.yarnpkg.com/del/-/del-6.1.1.tgz#3b70314f1ec0aa325c6b14eb36b95786671edb7a"
@ -9969,6 +9991,15 @@ fs-extra@^11.1.0:
jsonfile "^6.0.1"
universalify "^2.0.0"
fs-extra@^11.2.0:
version "11.3.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.3.0.tgz#0daced136bbaf65a555a326719af931adc7a314d"
integrity sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==
dependencies:
graceful-fs "^4.2.0"
jsonfile "^6.0.1"
universalify "^2.0.0"
fs-extra@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
@ -13203,6 +13234,13 @@ magic-string@^0.30.5:
dependencies:
"@jridgewell/sourcemap-codec" "^1.4.15"
magic-string@^0.30.9:
version "0.30.17"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453"
integrity sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==
dependencies:
"@jridgewell/sourcemap-codec" "^1.5.0"
make-dir@^1.0.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
@ -14911,7 +14949,7 @@ pathe@^0.3.0:
resolved "https://registry.yarnpkg.com/pathe/-/pathe-0.3.9.tgz#4baff768f37f03e3d9341502865fb93116f65191"
integrity sha512-6Y6s0vT112P3jD8dGfuS6r+lpa0qqNrLyHPOwvXMnyNTQaYiwgau2DP3aNDsR13xqtGj7rrPo+jFUATpU6/s+g==
pathe@^1.1.1:
pathe@^1.1.1, pathe@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.2.tgz#6c4cb47a945692e48a1ddd6e4094d170516437ec"
integrity sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==
@ -19158,6 +19196,11 @@ ufo@^1.3.1:
resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.3.2.tgz#c7d719d0628a1c80c006d2240e0d169f6e3c0496"
integrity sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==
ufo@^1.5.3:
version "1.6.1"
resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.6.1.tgz#ac2db1d54614d1b22c1d603e3aef44a85d8f146b"
integrity sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==
ufo@^1.5.4:
version "1.5.4"
resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.5.4.tgz#16d6949674ca0c9e0fbbae1fa20a71d7b1ded754"