mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-14 17:18:33 +00:00
Resolve "Introduce new chart widget type in the frontend"
This commit is contained in:
parent
c454cf61bf
commit
76c4dfa236
24 changed files with 393 additions and 14 deletions
backend/src/baserow/contrib/dashboard/widgets
enterprise
backend
src/baserow_enterprise
tests/baserow_enterprise_tests
web-frontend/modules/baserow_enterprise
web-frontend/modules
core/assets/scss/components/dashboard
dashboard
|
@ -1,6 +1,7 @@
|
||||||
from abc import ABC
|
from abc import ABC
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
|
from baserow.contrib.dashboard.models import Dashboard
|
||||||
from baserow.contrib.dashboard.types import WidgetDict
|
from baserow.contrib.dashboard.types import WidgetDict
|
||||||
from baserow.core.registry import (
|
from baserow.core.registry import (
|
||||||
CustomFieldsInstanceMixin,
|
CustomFieldsInstanceMixin,
|
||||||
|
@ -32,6 +33,17 @@ class WidgetType(
|
||||||
id_mapping_name = DASHBOARD_WIDGETS
|
id_mapping_name = DASHBOARD_WIDGETS
|
||||||
allowed_fields = ["title", "description"]
|
allowed_fields = ["title", "description"]
|
||||||
|
|
||||||
|
def before_create(self, dashboard: Dashboard):
|
||||||
|
"""
|
||||||
|
This function allows you to perform checks and operations
|
||||||
|
before a widget is created.
|
||||||
|
|
||||||
|
:param dashboard: The dashboard where the widget should be
|
||||||
|
created.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
def prepare_value_for_db(self, values: dict, instance: Widget | None = None):
|
def prepare_value_for_db(self, values: dict, instance: Widget | None = None):
|
||||||
"""
|
"""
|
||||||
This function allows you to hook into the moment a widget is created or
|
This function allows you to hook into the moment a widget is created or
|
||||||
|
|
|
@ -115,6 +115,8 @@ class WidgetService:
|
||||||
|
|
||||||
widget_type_from_registry = widget_type_registry.get(widget_type)
|
widget_type_from_registry = widget_type_registry.get(widget_type)
|
||||||
|
|
||||||
|
widget_type_from_registry.before_create(dashboard)
|
||||||
|
|
||||||
new_widget = self.handler.create_widget(
|
new_widget = self.handler.create_widget(
|
||||||
widget_type_from_registry,
|
widget_type_from_registry,
|
||||||
dashboard,
|
dashboard,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
from baserow_premium.license.handler import LicenseHandler
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from baserow.contrib.dashboard.data_sources.handler import DashboardDataSourceHandler
|
from baserow.contrib.dashboard.data_sources.handler import DashboardDataSourceHandler
|
||||||
|
@ -6,6 +7,7 @@ from baserow.contrib.dashboard.types import WidgetDict
|
||||||
from baserow.contrib.dashboard.widgets.models import Widget
|
from baserow.contrib.dashboard.widgets.models import Widget
|
||||||
from baserow.contrib.dashboard.widgets.registries import WidgetType
|
from baserow.contrib.dashboard.widgets.registries import WidgetType
|
||||||
from baserow.core.services.registries import service_type_registry
|
from baserow.core.services.registries import service_type_registry
|
||||||
|
from baserow_enterprise.features import CHART_WIDGET
|
||||||
from baserow_enterprise.integrations.local_baserow.service_types import (
|
from baserow_enterprise.integrations.local_baserow.service_types import (
|
||||||
LocalBaserowGroupedAggregateRowsUserServiceType,
|
LocalBaserowGroupedAggregateRowsUserServiceType,
|
||||||
)
|
)
|
||||||
|
@ -31,6 +33,11 @@ class ChartWidgetType(WidgetType):
|
||||||
class SerializedDict(WidgetDict):
|
class SerializedDict(WidgetDict):
|
||||||
data_source_id: int
|
data_source_id: int
|
||||||
|
|
||||||
|
def before_create(self, dashboard):
|
||||||
|
LicenseHandler.raise_if_workspace_doesnt_have_feature(
|
||||||
|
CHART_WIDGET, dashboard.workspace
|
||||||
|
)
|
||||||
|
|
||||||
def prepare_value_for_db(self, values: dict, instance: Widget | None = None):
|
def prepare_value_for_db(self, values: dict, instance: Widget | None = None):
|
||||||
if instance is None:
|
if instance is None:
|
||||||
# When the widget is being created we want to automatically
|
# When the widget is being created we want to automatically
|
||||||
|
|
|
@ -7,3 +7,4 @@ METRICS = "metrics"
|
||||||
SECURE_FILE_SERVE = "secure_file_serve"
|
SECURE_FILE_SERVE = "secure_file_serve"
|
||||||
ENTERPRISE_SETTINGS = "ENTERPRISE_SETTINGS"
|
ENTERPRISE_SETTINGS = "ENTERPRISE_SETTINGS"
|
||||||
DATA_SYNC = "data_sync"
|
DATA_SYNC = "data_sync"
|
||||||
|
CHART_WIDGET = "chart_widget"
|
||||||
|
|
|
@ -7,6 +7,7 @@ from baserow_premium.license.registries import LicenseType, SeatUsageSummary
|
||||||
from baserow.core.models import Workspace
|
from baserow.core.models import Workspace
|
||||||
from baserow_enterprise.features import (
|
from baserow_enterprise.features import (
|
||||||
AUDIT_LOG,
|
AUDIT_LOG,
|
||||||
|
CHART_WIDGET,
|
||||||
DATA_SYNC,
|
DATA_SYNC,
|
||||||
ENTERPRISE_SETTINGS,
|
ENTERPRISE_SETTINGS,
|
||||||
RBAC,
|
RBAC,
|
||||||
|
@ -32,6 +33,7 @@ class EnterpriseWithoutSupportLicenseType(LicenseType):
|
||||||
SECURE_FILE_SERVE,
|
SECURE_FILE_SERVE,
|
||||||
ENTERPRISE_SETTINGS,
|
ENTERPRISE_SETTINGS,
|
||||||
DATA_SYNC,
|
DATA_SYNC,
|
||||||
|
CHART_WIDGET,
|
||||||
]
|
]
|
||||||
instance_wide = True
|
instance_wide = True
|
||||||
seats_manually_assigned = False
|
seats_manually_assigned = False
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from django.test.utils import override_settings
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from rest_framework.reverse import reverse
|
from rest_framework.reverse import reverse
|
||||||
from rest_framework.status import HTTP_200_OK
|
from rest_framework.status import HTTP_200_OK
|
||||||
|
@ -6,7 +8,9 @@ from baserow.test_utils.helpers import AnyInt
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
|
@override_settings(DEBUG=True)
|
||||||
def test_create_chart_widget(api_client, enterprise_data_fixture):
|
def test_create_chart_widget(api_client, enterprise_data_fixture):
|
||||||
|
enterprise_data_fixture.enable_enterprise()
|
||||||
user, token = enterprise_data_fixture.create_user_and_token()
|
user, token = enterprise_data_fixture.create_user_and_token()
|
||||||
dashboard = enterprise_data_fixture.create_dashboard_application(user=user)
|
dashboard = enterprise_data_fixture.create_dashboard_application(user=user)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db.models.deletion import ProtectedError
|
from django.db.models.deletion import ProtectedError
|
||||||
|
from django.test.utils import override_settings
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -14,7 +15,9 @@ from baserow_enterprise.integrations.local_baserow.models import (
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
|
@override_settings(DEBUG=True)
|
||||||
def test_create_chart_widget_creates_data_source(enterprise_data_fixture):
|
def test_create_chart_widget_creates_data_source(enterprise_data_fixture):
|
||||||
|
enterprise_data_fixture.enable_enterprise()
|
||||||
user = enterprise_data_fixture.create_user()
|
user = enterprise_data_fixture.create_user()
|
||||||
dashboard = enterprise_data_fixture.create_dashboard_application(user=user)
|
dashboard = enterprise_data_fixture.create_dashboard_application(user=user)
|
||||||
widget_type = "chart"
|
widget_type = "chart"
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
<svg width="72" height="48" viewBox="0 0 72 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="1.25" y="1.25" width="69.5" height="45.5" rx="4.75" fill="white"/>
|
||||||
|
<rect x="1.25" y="1.25" width="69.5" height="45.5" rx="4.75" stroke="#E6E6E7" stroke-width="1.5"/>
|
||||||
|
<rect opacity="0.48" x="8" y="15.5" width="6" height="4" rx="2" fill="#E6E6E7"/>
|
||||||
|
<rect opacity="0.48" x="8" y="37" width="6" height="4" rx="2" fill="#E6E6E7"/>
|
||||||
|
<rect opacity="0.48" x="8" y="26.5" width="8" height="4" rx="2" fill="#E6E6E7"/>
|
||||||
|
<rect x="24" y="8" width="1" height="32" fill="#EDEDED"/>
|
||||||
|
<rect x="20" y="39" width="44" height="1" fill="#EDEDED"/>
|
||||||
|
<rect x="20" y="28" width="44" height="1" fill="#EDEDED"/>
|
||||||
|
<rect x="20" y="17" width="44" height="1" fill="#EDEDED"/>
|
||||||
|
<path d="M30 37C30 35.8954 30.8954 35 32 35H34C35.1046 35 36 35.8954 36 37V40H30V37Z" fill="#4E5CFE"/>
|
||||||
|
<path d="M42 25C42 23.8954 42.8954 23 44 23H46C47.1046 23 48 23.8954 48 25V40H42V25Z" fill="#4E5CFE"/>
|
||||||
|
<path d="M54 13C54 11.8954 54.8954 11 56 11H58C59.1046 11 60 11.8954 60 13V40H54V13Z" fill="#4E5CFE"/>
|
||||||
|
</svg>
|
After (image error) Size: 1.1 KiB |
|
@ -18,3 +18,4 @@
|
||||||
@import 'common_oidc_setting_form';
|
@import 'common_oidc_setting_form';
|
||||||
@import 'saml_auth_link';
|
@import 'saml_auth_link';
|
||||||
@import 'oidc_auth_link';
|
@import 'oidc_auth_link';
|
||||||
|
@import 'dashboard_chart_widget';
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
.dashboard-chart-widget {
|
||||||
|
padding: 0 24px 24px;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
<template><div></div></template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import form from '@baserow/modules/core/mixins/form'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'GroupedAggregateRowsDataSourceForm',
|
||||||
|
mixins: [form],
|
||||||
|
props: {
|
||||||
|
dashboard: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
widget: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
dataSource: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
storePrefix: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,83 @@
|
||||||
|
<template>
|
||||||
|
<div class="dashboard-chart-widget">
|
||||||
|
<div class="widget-header">
|
||||||
|
<div class="widget-header__main">
|
||||||
|
<div class="widget-header__title-wrapper">
|
||||||
|
<div class="widget-header__title">{{ widget.title }}</div>
|
||||||
|
<div
|
||||||
|
v-if="dataSourceMisconfigured"
|
||||||
|
class="widget-header__fix-configuration"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="5"
|
||||||
|
height="6"
|
||||||
|
viewBox="0 0 5 6"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<circle cx="2.5" cy="3" r="2.5" fill="#FF5A44" />
|
||||||
|
</svg>
|
||||||
|
{{ $t('widget.fixConfiguration') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="widget.description" class="widget-header__description">
|
||||||
|
{{ widget.description }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<WidgetContextMenu
|
||||||
|
v-if="isEditMode"
|
||||||
|
:widget="widget"
|
||||||
|
:dashboard="dashboard"
|
||||||
|
@delete-widget="$emit('delete-widget', $event)"
|
||||||
|
></WidgetContextMenu>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import WidgetContextMenu from '@baserow/modules/dashboard/components/widget/WidgetContextMenu'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ChartWidget',
|
||||||
|
components: { WidgetContextMenu },
|
||||||
|
props: {
|
||||||
|
dashboard: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
widget: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
storePrefix: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
dataSource() {
|
||||||
|
return this.$store.getters[
|
||||||
|
`${this.storePrefix}dashboardApplication/getDataSourceById`
|
||||||
|
](this.widget.data_source_id)
|
||||||
|
},
|
||||||
|
dataForDataSource() {
|
||||||
|
return this.$store.getters[
|
||||||
|
`${this.storePrefix}dashboardApplication/getDataForDataSource`
|
||||||
|
](this.dataSource?.id)
|
||||||
|
},
|
||||||
|
isEditMode() {
|
||||||
|
return this.$store.getters[
|
||||||
|
`${this.storePrefix}dashboardApplication/isEditMode`
|
||||||
|
]
|
||||||
|
},
|
||||||
|
dataSourceMisconfigured() {
|
||||||
|
const data = this.dataForDataSource
|
||||||
|
if (data) {
|
||||||
|
return !!data._error
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,75 @@
|
||||||
|
<template>
|
||||||
|
<GroupedAggregateRowsDataSourceForm
|
||||||
|
v-if="dataSource"
|
||||||
|
ref="dataSourceForm"
|
||||||
|
:dashboard="dashboard"
|
||||||
|
:widget="widget"
|
||||||
|
:data-source="dataSource"
|
||||||
|
:default-values="dataSource"
|
||||||
|
:store-prefix="storePrefix"
|
||||||
|
@values-changed="onDataSourceValuesChanged"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import GroupedAggregateRowsDataSourceForm from '@baserow_enterprise/dashboard/components/data_source/GroupedAggregateRowsDataSourceForm'
|
||||||
|
import error from '@baserow/modules/core/mixins/error'
|
||||||
|
import { notifyIf } from '@baserow/modules/core/utils/error'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ChartWidgetSettings',
|
||||||
|
components: { GroupedAggregateRowsDataSourceForm },
|
||||||
|
mixins: [error],
|
||||||
|
props: {
|
||||||
|
dashboard: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
widget: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
storePrefix: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
dataSource() {
|
||||||
|
return this.$store.getters[
|
||||||
|
`${this.storePrefix}dashboardApplication/getDataSourceById`
|
||||||
|
](this.widget.data_source_id)
|
||||||
|
},
|
||||||
|
integration() {
|
||||||
|
return this.$store.getters[
|
||||||
|
`${this.storePrefix}dashboardApplication/getIntegrationById`
|
||||||
|
](this.dataSource.integration_id)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async onDataSourceValuesChanged(changedDataSourceValues) {
|
||||||
|
if (this.$refs.dataSourceForm.isFormValid()) {
|
||||||
|
try {
|
||||||
|
await this.$store.dispatch(
|
||||||
|
`${this.storePrefix}dashboardApplication/updateDataSource`,
|
||||||
|
{
|
||||||
|
dataSourceId: this.dataSource.id,
|
||||||
|
values: changedDataSourceValues,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
this.$refs.dataSourceForm.reset()
|
||||||
|
this.$refs.dataSourceForm.touch()
|
||||||
|
notifyIf(error, 'dashboard')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,44 @@
|
||||||
|
import { WidgetType } from '@baserow/modules/dashboard/widgetTypes'
|
||||||
|
import ChartWidget from '@baserow_enterprise/dashboard/components/widget/ChartWidget'
|
||||||
|
import ChartWidgetSettings from '@baserow_enterprise/dashboard/components/widget/ChartWidgetSettings'
|
||||||
|
import ChartWidgetSvg from '@baserow_enterprise/assets/images/dashboard/widgets/chart_widget.svg'
|
||||||
|
import EnterpriseFeatures from '@baserow_enterprise/features'
|
||||||
|
import EnterpriseModal from '@baserow_enterprise/components/EnterpriseModal'
|
||||||
|
|
||||||
|
export class ChartWidgetType extends WidgetType {
|
||||||
|
static getType() {
|
||||||
|
return 'chart'
|
||||||
|
}
|
||||||
|
|
||||||
|
get name() {
|
||||||
|
return this.app.i18n.t('chartWidget.name')
|
||||||
|
}
|
||||||
|
|
||||||
|
get createWidgetImage() {
|
||||||
|
return ChartWidgetSvg
|
||||||
|
}
|
||||||
|
|
||||||
|
get component() {
|
||||||
|
return ChartWidget
|
||||||
|
}
|
||||||
|
|
||||||
|
get settingsComponent() {
|
||||||
|
return ChartWidgetSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading(widget, data) {
|
||||||
|
const dataSourceId = widget.data_source_id
|
||||||
|
if (data[dataSourceId] && Object.keys(data[dataSourceId]).length !== 0) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
isAvailable(workspaceId) {
|
||||||
|
return this.app.$hasFeature(EnterpriseFeatures.CHART_WIDGET, workspaceId)
|
||||||
|
}
|
||||||
|
|
||||||
|
getDeactivatedModal() {
|
||||||
|
return EnterpriseModal
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ const EnterpriseFeatures = {
|
||||||
AUDIT_LOG: 'AUDIT_LOG',
|
AUDIT_LOG: 'AUDIT_LOG',
|
||||||
ENTERPRISE_SETTINGS: 'ENTERPRISE_SETTINGS',
|
ENTERPRISE_SETTINGS: 'ENTERPRISE_SETTINGS',
|
||||||
DATA_SYNC: 'DATA_SYNC',
|
DATA_SYNC: 'DATA_SYNC',
|
||||||
|
CHART_WIDGET: 'CHART_WIDGET',
|
||||||
}
|
}
|
||||||
|
|
||||||
export default EnterpriseFeatures
|
export default EnterpriseFeatures
|
||||||
|
|
|
@ -44,6 +44,7 @@ export class EnterpriseWithoutSupportLicenseType extends LicenseType {
|
||||||
EnterpriseFeaturesObject.AUDIT_LOG,
|
EnterpriseFeaturesObject.AUDIT_LOG,
|
||||||
EnterpriseFeaturesObject.ENTERPRISE_SETTINGS,
|
EnterpriseFeaturesObject.ENTERPRISE_SETTINGS,
|
||||||
EnterpriseFeaturesObject.DATA_SYNC,
|
EnterpriseFeaturesObject.DATA_SYNC,
|
||||||
|
EnterpriseFeaturesObject.CHART_WIDGET,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -439,5 +439,8 @@
|
||||||
},
|
},
|
||||||
"oidcAuthLink": {
|
"oidcAuthLink": {
|
||||||
"placeholderWithOIDC": "{login} with {provider}"
|
"placeholderWithOIDC": "{login} with {provider}"
|
||||||
|
},
|
||||||
|
"chartWidget": {
|
||||||
|
"name": "Chart"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,9 +50,13 @@ import {
|
||||||
GitLabIssuesDataSyncType,
|
GitLabIssuesDataSyncType,
|
||||||
HubspotContactsDataSyncType,
|
HubspotContactsDataSyncType,
|
||||||
} from '@baserow_enterprise/dataSyncTypes'
|
} from '@baserow_enterprise/dataSyncTypes'
|
||||||
|
import { ChartWidgetType } from '@baserow_enterprise/dashboard/widgetTypes'
|
||||||
import { PeriodicIntervalFieldsConfigureDataSyncType } from '@baserow_enterprise/configureDataSyncTypes'
|
import { PeriodicIntervalFieldsConfigureDataSyncType } from '@baserow_enterprise/configureDataSyncTypes'
|
||||||
|
|
||||||
import { FF_AB_SSO } from '@baserow/modules/core/plugins/featureFlags'
|
import {
|
||||||
|
FF_AB_SSO,
|
||||||
|
FF_DASHBOARDS,
|
||||||
|
} from '@baserow/modules/core/plugins/featureFlags'
|
||||||
|
|
||||||
export default (context) => {
|
export default (context) => {
|
||||||
const { app, isDev, store } = context
|
const { app, isDev, store } = context
|
||||||
|
@ -155,4 +159,8 @@ export default (context) => {
|
||||||
'configureDataSync',
|
'configureDataSync',
|
||||||
new PeriodicIntervalFieldsConfigureDataSyncType(context)
|
new PeriodicIntervalFieldsConfigureDataSyncType(context)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (app.$featureFlagIsEnabled(FF_DASHBOARDS)) {
|
||||||
|
app.$registry.register('dashboardWidget', new ChartWidgetType(context))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,3 +9,4 @@
|
||||||
@import 'create_widget_button';
|
@import 'create_widget_button';
|
||||||
@import 'widget_header';
|
@import 'widget_header';
|
||||||
@import 'widget_settings_base_form';
|
@import 'widget_settings_base_form';
|
||||||
|
@import 'create_widget_modal';
|
||||||
|
|
|
@ -13,6 +13,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.create-widget-card__name {
|
.create-widget-card__name {
|
||||||
|
height: 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
color: $palette-neutral-1200;
|
color: $palette-neutral-1200;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
|
@ -20,8 +25,17 @@
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.create-widget-card__name-locked {
|
||||||
|
display: inline-block;
|
||||||
|
color: #4e5cfe;
|
||||||
|
padding: 3px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: rgba(78, 92, 254, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
.create-widget-card__img-container {
|
.create-widget-card__img-container {
|
||||||
padding: 16px 24px;
|
height: 80px;
|
||||||
|
padding: 0 24px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -31,7 +45,7 @@
|
||||||
box-shadow: 0 1px 2px 0 rgba(7, 8, 16, 0.1);
|
box-shadow: 0 1px 2px 0 rgba(7, 8, 16, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.create-widget-card:hover .create-widget-card__img-container {
|
.create-widget-card--available:hover .create-widget-card__img-container {
|
||||||
border: 1px solid #d7d8d9;
|
border: 1px solid #d7d8d9;
|
||||||
background: #f7f7f7;
|
background: #f7f7f7;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
.create-widget-modal__cards {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 22px;
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
<template>
|
||||||
|
<a
|
||||||
|
class="create-widget-card"
|
||||||
|
:class="{
|
||||||
|
'create-widget-card--available': isWidgetAvailable,
|
||||||
|
}"
|
||||||
|
@click="widgetTypeSelected"
|
||||||
|
>
|
||||||
|
<div class="create-widget-card__img-container">
|
||||||
|
<img :src="widgetType.createWidgetImage" />
|
||||||
|
</div>
|
||||||
|
<div class="create-widget-card__name">
|
||||||
|
<span>{{ widgetType.name }}</span>
|
||||||
|
<span v-if="!isWidgetAvailable">
|
||||||
|
<i class="iconoir-lock create-widget-card__name-locked"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<component
|
||||||
|
:is="deactivatedModal"
|
||||||
|
v-if="deactivatedModal != null"
|
||||||
|
ref="deactivatedModal"
|
||||||
|
:name="widgetType.name"
|
||||||
|
:workspace="dashboard.workspace"
|
||||||
|
></component>
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'CreateWidgetCard',
|
||||||
|
props: {
|
||||||
|
dashboard: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
widgetType: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isWidgetAvailable() {
|
||||||
|
return this.widgetType.isAvailable(this.dashboard.workspace.id)
|
||||||
|
},
|
||||||
|
deactivatedModal() {
|
||||||
|
return this.widgetType.getDeactivatedModal()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
widgetTypeSelected() {
|
||||||
|
if (!this.isWidgetAvailable) {
|
||||||
|
this.$refs.deactivatedModal.show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$emit('widget-type-selected', this.widgetType.type)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -3,29 +3,26 @@
|
||||||
<h2 class="box__title">
|
<h2 class="box__title">
|
||||||
{{ $t('createWidgetModal.title') }}
|
{{ $t('createWidgetModal.title') }}
|
||||||
</h2>
|
</h2>
|
||||||
<div>
|
<div class="create-widget-modal__cards">
|
||||||
<a
|
<CreateWidgetCard
|
||||||
v-for="widgetType in widgetTypes"
|
v-for="widgetType in widgetTypes"
|
||||||
:key="widgetType.type"
|
:key="widgetType.type"
|
||||||
class="create-widget-card"
|
:dashboard="dashboard"
|
||||||
@click="widgetTypeSelected(widgetType.type)"
|
:widget-type="widgetType"
|
||||||
|
@widget-type-selected="widgetTypeSelected"
|
||||||
>
|
>
|
||||||
<div class="create-widget-card__img-container">
|
</CreateWidgetCard>
|
||||||
<img :src="widgetType.createWidgetImage" />
|
|
||||||
</div>
|
|
||||||
<div class="create-widget-card__name">
|
|
||||||
{{ widgetType.name }}
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import modal from '@baserow/modules/core/mixins/modal'
|
import modal from '@baserow/modules/core/mixins/modal'
|
||||||
|
import CreateWidgetCard from '@baserow/modules/dashboard/components/CreateWidgetCard'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CreateWidgetModal',
|
name: 'CreateWidgetModal',
|
||||||
|
components: { CreateWidgetCard },
|
||||||
mixins: [modal],
|
mixins: [modal],
|
||||||
props: {
|
props: {
|
||||||
dashboard: {
|
dashboard: {
|
||||||
|
|
|
@ -36,6 +36,14 @@ export class WidgetType extends Registerable {
|
||||||
isLoading(widget, data) {
|
isLoading(widget, data) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isAvailable() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
getDeactivatedModal() {
|
||||||
|
return null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SummaryWidgetType extends WidgetType {
|
export class SummaryWidgetType extends WidgetType {
|
||||||
|
|
Loading…
Add table
Reference in a new issue