/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import React, { FunctionComponent, useState, useRef } from 'react'; import Alert from 'src/components/Alert'; import Button from 'src/components/Button'; import { FeatureFlag, styled, SupersetClient, t } from '@superset-ui/core'; import Modal from 'src/components/Modal'; import AsyncEsmComponent from 'src/components/AsyncEsmComponent'; import { isFeatureEnabled } from 'src/featureFlags'; import { getClientErrorObject } from 'src/utils/getClientErrorObject'; import withToasts from 'src/components/MessageToasts/withToasts'; const DatasourceEditor = AsyncEsmComponent(() => import('./DatasourceEditor')); const StyledDatasourceModal = styled(Modal)` .modal-content { height: 900px; display: flex; flex-direction: column; align-items: stretch; } .modal-header { flex: 0 1 auto; } .modal-body { flex: 1 1 auto; overflow: auto; } .modal-footer { flex: 0 1 auto; } `; interface DatasourceModalProps { addSuccessToast: (msg: string) => void; datasource: any; onChange: () => {}; onDatasourceSave: (datasource: object, errors?: Array) => {}; onHide: () => {}; show: boolean; } function buildExtraJsonObject(item: Record) { const certification = item?.certified_by || item?.certification_details ? { certified_by: item?.certified_by, details: item?.certification_details, } : undefined; return JSON.stringify({ certification, warning_markdown: item?.warning_markdown, }); } const DatasourceModal: FunctionComponent = ({ addSuccessToast, datasource, onDatasourceSave, onHide, show, }) => { const [currentDatasource, setCurrentDatasource] = useState(datasource); const [errors, setErrors] = useState([]); const [isSaving, setIsSaving] = useState(false); const [isEditing, setIsEditing] = useState(false); const dialog = useRef(null); const [modal, contextHolder] = Modal.useModal(); const onConfirmSave = () => { // Pull out extra fields into the extra object const schema = currentDatasource.tableSelector?.schema || currentDatasource.databaseSelector?.schema || currentDatasource.schema; setIsSaving(true); SupersetClient.put({ endpoint: `/api/v1/dataset/${currentDatasource.id}`, jsonPayload: { table_name: currentDatasource.table_name, database_id: currentDatasource.database?.id, sql: currentDatasource.sql, filter_select_enabled: currentDatasource.filter_select_enabled, fetch_values_predicate: currentDatasource.fetch_values_predicate, schema, description: currentDatasource.description, main_dttm_col: currentDatasource.main_dttm_col, offset: currentDatasource.offset, default_endpoint: currentDatasource.default_endpoint, cache_timeout: currentDatasource.cache_timeout === '' ? null : currentDatasource.cache_timeout, is_sqllab_view: currentDatasource.is_sqllab_view, template_params: currentDatasource.template_params, extra: currentDatasource.extra, is_managed_externally: currentDatasource.is_managed_externally, external_url: currentDatasource.external_url, metrics: currentDatasource?.metrics?.map( (metric: Record) => { const metricBody: any = { expression: metric.expression, description: metric.description, metric_name: metric.metric_name, metric_type: metric.metric_type, d3format: metric.d3format, verbose_name: metric.verbose_name, warning_text: metric.warning_text, uuid: metric.uuid, extra: buildExtraJsonObject(metric), }; if (!Number.isNaN(Number(metric.id))) { metricBody.id = metric.id; } return metricBody; }, ), columns: currentDatasource?.columns?.map( (column: Record) => ({ id: typeof column.id === 'number' ? column.id : undefined, column_name: column.column_name, type: column.type, advanced_data_type: column.advanced_data_type, verbose_name: column.verbose_name, description: column.description, expression: column.expression, filterable: column.filterable, groupby: column.groupby, is_active: column.is_active, is_dttm: column.is_dttm, python_date_format: column.python_date_format || null, uuid: column.uuid, extra: buildExtraJsonObject(column), }), ), owners: currentDatasource.owners.map( (o: Record) => o.value || o.id, ), }, }) .then(() => { addSuccessToast(t('The dataset has been saved')); return SupersetClient.get({ endpoint: `/api/v1/dataset/${currentDatasource?.id}`, }); }) .then(({ json }) => { // eslint-disable-next-line no-param-reassign json.result.type = 'table'; onDatasourceSave({ ...json.result, owners: currentDatasource.owners, }); onHide(); }) .catch(response => { setIsSaving(false); getClientErrorObject(response).then(({ error }) => { modal.error({ title: t('Error'), content: error || t('An error has occurred'), okButtonProps: { danger: true, className: 'btn-danger' }, }); }); }); }; const onDatasourceChange = (data: Record, err: Array) => { setCurrentDatasource({ ...data, metrics: data?.metrics.map((metric: Record) => ({ ...metric, is_certified: metric?.certified_by || metric?.certification_details, })), }); setErrors(err); }; const renderSaveDialog = () => (
({ marginTop: theme.gridUnit * 4, marginBottom: theme.gridUnit * 4, })} type="warning" showIcon message={t(`The dataset configuration exposed here affects all the charts using this dataset. Be mindful that changing settings here may affect other charts in undesirable ways.`)} /> {t('Are you sure you want to save and apply changes?')}
); const onClickSave = () => { dialog.current = modal.confirm({ title: t('Confirm save'), content: renderSaveDialog(), onOk: onConfirmSave, icon: null, okText: t('OK'), cancelText: t('Cancel'), }); }; const showLegacyDatasourceEditor = !isFeatureEnabled( FeatureFlag.DISABLE_LEGACY_DATASOURCE_EDITOR, ); return ( {t('Edit Dataset ')} {currentDatasource.table_name} } maskClosable={!isEditing} footer={ <> {showLegacyDatasourceEditor && ( )} } responsive > {contextHolder} ); }; export default withToasts(DatasourceModal);