1
0
Fork 0
mirror of https://gitlab.com/bramw/baserow.git synced 2025-04-14 17:18:33 +00:00

Merge branch '1567-i-can-create-a-builder-application' into 'develop'

Resolve "I can create a Builder application"

Closes 

See merge request 
This commit is contained in:
Alexander Haller 2023-02-10 12:15:59 +00:00
commit 1a80d51ffb
19 changed files with 404 additions and 1 deletions

View file

@ -1,7 +1,14 @@
from django.db import transaction
from django.db.transaction import Atomic
from baserow.contrib.builder.models import Builder
from baserow.core.models import Application
from baserow.core.registries import ApplicationType
class BuilderApplicationType(ApplicationType):
type = "builder"
model_class = Builder
def export_safe_transaction_context(self, application: Application) -> Atomic:
return transaction.atomic()

View file

@ -1,3 +1,4 @@
from baserow.contrib.builder.models import Builder
from baserow.contrib.database.models import Database
@ -13,3 +14,15 @@ class ApplicationFixtures:
kwargs["order"] = 0
return Database.objects.create(**kwargs)
def create_builder_application(self, user=None, **kwargs):
if "group" not in kwargs:
kwargs["group"] = self.create_group(user=user)
if "name" not in kwargs:
kwargs["name"] = self.fake.name()
if "order" not in kwargs:
kwargs["order"] = 0
return Builder.objects.create(**kwargs)

View file

@ -20,12 +20,38 @@ from baserow.core.job_types import DuplicateApplicationJobType
from baserow.core.jobs.handler import JobHandler
from baserow.core.models import Template
from baserow.core.operations import ListApplicationsGroupOperationType
from baserow.core.registries import application_type_registry
def stub_filter_queryset(u, o, q, **kwargs):
return q
@pytest.mark.django_db()
@pytest.mark.parametrize("application_type", application_type_registry.get_all())
def test_can_create_different_application_types(
application_type, api_client, data_fixture
):
user, token = data_fixture.create_user_and_token()
group = data_fixture.create_group(user=user)
response = api_client.post(
reverse("api:applications:list", kwargs={"group_id": group.id}),
{"name": "Test 1", "type": application_type.type},
format="json",
HTTP_AUTHORIZATION=f"JWT {token}",
)
application = application_type.model_class.objects.all()[0]
response_json = response.json()
assert response.status_code == HTTP_200_OK
assert response_json["type"] == application_type.type
assert response_json["id"] == application.id
assert response_json["name"] == application.name
assert response_json["order"] == application.order
@pytest.mark.django_db
def test_list_applications(api_client, data_fixture, django_assert_num_queries):
user, token = data_fixture.create_user_and_token(

View file

@ -0,0 +1,17 @@
import pytest
from baserow.contrib.builder.models import Builder
from baserow.core.handler import CoreHandler
@pytest.mark.django_db
def test_can_duplicate_builder_application(data_fixture):
user = data_fixture.create_user()
builder = data_fixture.create_builder_application(user=user)
builder_clone = CoreHandler().duplicate_application(user, builder)
assert builder.id != builder_clone.id
assert builder.name in builder_clone.name
assert Builder.objects.count() == 2

View file

@ -0,0 +1,52 @@
import pytest
from baserow.contrib.builder.models import Builder
from baserow.core.models import Snapshot
from baserow.core.snapshots.handler import SnapshotHandler
from baserow.core.utils import Progress
@pytest.mark.django_db
def test_can_create_a_snapshot_for_builder_application(data_fixture):
user = data_fixture.create_user()
builder = data_fixture.create_builder_application(user)
snapshot = SnapshotHandler().create(builder.id, performed_by=user, name="test")[
"snapshot"
]
assert snapshot is not None
assert snapshot.name == "test"
assert Snapshot.objects.count() == 1
@pytest.mark.django_db
def test_can_delete_a_snapshot_for_builder_application(data_fixture):
user = data_fixture.create_user()
builder = data_fixture.create_builder_application(user)
snapshot = SnapshotHandler().create(builder.id, performed_by=user, name="test")[
"snapshot"
]
SnapshotHandler().delete(snapshot.id, user)
snapshot.refresh_from_db()
assert snapshot.mark_for_deletion is True
@pytest.mark.django_db
def test_can_restore_a_snapshot_for_builder_application(data_fixture):
user = data_fixture.create_user()
builder = data_fixture.create_builder_application(user)
snapshot = data_fixture.create_snapshot(
snapshot_from_application=builder,
snapshot_to_application=builder,
user=user,
)
snapshot_restored = SnapshotHandler().perform_restore(snapshot, Progress(total=100))
assert snapshot_restored is not None
assert Builder.objects.count() == 2

View file

@ -0,0 +1,7 @@
{
"type": "feature",
"message": "Add ability to create application builder",
"issue_number": 1567,
"bullet_points": [],
"created_at": "2023-02-10"
}

View file

@ -1,4 +1,6 @@
import { ApplicationType } from '@baserow/modules/core/applicationTypes'
import BuilderForm from '@baserow/modules/builder/components/form/BuilderForm'
import SidebarComponentBuilder from '@baserow/modules/builder/components/sidebar/SidebarComponentBuilder'
export class BuilderApplicationType extends ApplicationType {
static getType() {
@ -18,4 +20,12 @@ export class BuilderApplicationType extends ApplicationType {
const { i18n } = this.app
return i18n.t('applicationType.builderDefaultName')
}
getApplicationFormComponent() {
return BuilderForm
}
getSidebarComponent() {
return SidebarComponentBuilder
}
}

View file

@ -0,0 +1,65 @@
import { Registerable } from '@baserow/modules/core/registry'
import IntegrationSettings from '@baserow/modules/builder/components/settings/IntegrationSettings'
import ThemeSettings from '@baserow/modules/builder/components/settings/ThemeSettings'
class BuilderSettingType extends Registerable {
getType() {
return null
}
getName() {
return null
}
getIconClass() {
return null
}
getComponent() {
return null
}
}
export class IntegrationsBuilderSettingsType extends BuilderSettingType {
getType() {
return 'integrations'
}
getName() {
return this.app.i18n.t('builderSettingTypes.integrationsName')
}
getIconClass() {
return 'plug'
}
getOrder() {
return 1
}
getComponent() {
return IntegrationSettings
}
}
export class ThemeBuilderSettingsType extends BuilderSettingType {
getType() {
return 'theme'
}
getName() {
return this.app.i18n.t('builderSettingTypes.themeName')
}
getIconClass() {
return 'tint'
}
getOrder() {
return 2
}
getComponent() {
return ThemeSettings
}
}

View file

@ -0,0 +1,35 @@
<template>
<ApplicationForm
:default-values="{ name: defaultName }"
@submitted="$emit('submitted', $event)"
>
<FormElement class="builder-form__controls">
<button
class="button button--large"
:class="{ 'button--loading': loading }"
:disabled="loading"
type="submit"
>
{{ $t('builderForm.submit') }}
</button>
</FormElement>
</ApplicationForm>
</template>
<script>
import ApplicationForm from '@baserow/modules/core/components/application/ApplicationForm'
export default {
name: 'BuilderForm',
components: { ApplicationForm },
props: {
defaultName: {
type: String,
required: true,
},
loading: {
type: Boolean,
required: true,
},
},
}
</script>

View file

@ -0,0 +1,59 @@
<template>
<Modal left-sidebar left-sidebar-scrollable>
<template #sidebar>
<div class="modal-sidebar__head">
<div class="modal-sidebar__head-name">
{{ $t('builderSettingsModal.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="fas modal-sidebar__nav-icon"
:class="'fa-' + setting.getIconClass()"
></i>
{{ setting.getName() }}
</a>
</li>
</ul>
</template>
<template v-if="settingSelected" #content>
<component :is="settingSelected.getComponent()"></component>
</template>
</Modal>
</template>
<script>
import modal from '@baserow/modules/core/mixins/modal'
export default {
name: 'BuilderSettingsModal',
mixins: [modal],
data() {
return {
settingSelected: null,
}
},
computed: {
registeredSettings() {
return this.$registry.getOrderedList('builderSettings')
},
},
methods: {
setPage(page) {
this.page = page
},
show(...args) {
if (!this.settingSelected) {
this.settingSelected = this.registeredSettings[0]
}
return modal.methods.show.call(this, ...args)
},
},
}
</script>

View file

@ -0,0 +1,9 @@
<template>
<div>INTEGRATIONS</div>
</template>
<script>
export default {
name: 'IntegrationSettings',
}
</script>

View file

@ -0,0 +1,9 @@
<template>
<div>THEME</div>
</template>
<script>
export default {
name: 'ThemeSettings',
}
</script>

View file

@ -0,0 +1,57 @@
<template>
<div>
<SidebarApplication
ref="sidebarApplication"
:group="group"
:application="application"
@selected="selected"
>
<template #context>
<li
v-if="
$hasPermission(
'application.update',
application,
application.group.id
)
"
>
<a @click="settingsClicked">
<i class="context__menu-icon fas fa-fw fa-cog"></i>
{{ $t('sidebarComponentBuilder.settings') }}
</a>
</li>
</template>
</SidebarApplication>
<BuilderSettingsModal ref="builderSettingsModal"></BuilderSettingsModal>
</div>
</template>
<script>
import SidebarApplication from '@baserow/modules/core/components/sidebar/SidebarApplication'
import BuilderSettingsModal from '@baserow/modules/builder/components/settings/BuilderSettingsModal'
export default {
name: 'TemplateSidebar',
components: { BuilderSettingsModal, SidebarApplication },
props: {
application: {
type: Object,
required: true,
},
group: {
type: Object,
required: true,
},
},
methods: {
selected() {
console.log('TODO')
},
settingsClicked() {
this.$refs.sidebarApplication.$refs.context.hide()
this.$refs.builderSettingsModal.show()
},
},
}
</script>

View file

@ -1 +1,15 @@
{}
{
"builderForm": {
"submit": "Add application"
},
"sidebarComponentBuilder": {
"settings": "Settings"
},
"builderSettingsModal": {
"title": "Application"
},
"builderSettingTypes": {
"integrationsName": "Integrations",
"themeName": "Theme"
}
}

View file

@ -6,6 +6,10 @@ import de from '@baserow/modules/builder/locales/de.json'
import es from '@baserow/modules/builder/locales/es.json'
import it from '@baserow/modules/builder/locales/it.json'
import pl from '@baserow/modules/builder/locales/pl.json'
import {
IntegrationsBuilderSettingsType,
ThemeBuilderSettingsType,
} from '@baserow/modules/builder/builderSettingTypes'
export default (context) => {
const { app, isDev } = context
@ -22,5 +26,16 @@ export default (context) => {
i18n.mergeLocaleMessage('pl', pl)
}
app.$registry.registerNamespace('builderSettings')
app.$registry.register('application', new BuilderApplicationType(context))
app.$registry.register(
'builderSettings',
new IntegrationsBuilderSettingsType(context)
)
app.$registry.register(
'builderSettings',
new ThemeBuilderSettingsType(context)
)
}

View file

@ -113,3 +113,4 @@
@import 'group_invite_form';
@import 'settings/members/edit_role_context';
@import 'badge';
@import 'builder/all';

View file

@ -0,0 +1 @@
@import 'builder_form';

View file

@ -0,0 +1,4 @@
.builder-form__controls {
display: flex;
justify-content: flex-end;
}

View file

@ -67,6 +67,8 @@ export default {
})
} catch (error) {
this.handleError(error, 'application')
} finally {
this.loading = false
}
},
},