| /** |
| * 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 { styled, t, SupersetClient } from '@superset-ui/core'; |
| |
| import Modal from 'src/components/Modal'; |
| import AsyncEsmComponent from 'src/components/AsyncEsmComponent'; |
| import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags'; |
| |
| import { getClientErrorObject } from 'src/utils/getClientErrorObject'; |
| import withToasts from 'src/messageToasts/enhancers/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; |
| } |
| |
| .ant-modal-body { |
| overflow: visible; |
| } |
| `; |
| |
| interface DatasourceModalProps { |
| addSuccessToast: (msg: string) => void; |
| datasource: any; |
| onChange: () => {}; |
| onDatasourceSave: (datasource: object, errors?: Array<any>) => {}; |
| onHide: () => {}; |
| show: boolean; |
| } |
| |
| function buildMetricExtraJsonObject(metric: Record<string, unknown>) { |
| const certification = |
| metric?.certified_by || metric?.certification_details |
| ? { |
| certified_by: metric?.certified_by, |
| details: metric?.certification_details, |
| } |
| : undefined; |
| return JSON.stringify({ |
| certification, |
| warning_markdown: metric?.warning_markdown, |
| }); |
| } |
| |
| const DatasourceModal: FunctionComponent<DatasourceModalProps> = ({ |
| addSuccessToast, |
| datasource, |
| onDatasourceSave, |
| onHide, |
| show, |
| }) => { |
| const [currentDatasource, setCurrentDatasource] = useState(datasource); |
| const [errors, setErrors] = useState<any[]>([]); |
| const [isSaving, setIsSaving] = useState(false); |
| const dialog = useRef<any>(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.post({ |
| endpoint: '/datasource/save/', |
| postPayload: { |
| data: { |
| ...currentDatasource, |
| schema, |
| metrics: currentDatasource?.metrics?.map( |
| (metric: Record<string, unknown>) => ({ |
| ...metric, |
| extra: buildMetricExtraJsonObject(metric), |
| }), |
| ), |
| type: currentDatasource.type || currentDatasource.datasource_type, |
| }, |
| }, |
| }) |
| .then(({ json }) => { |
| addSuccessToast(t('The dataset has been saved')); |
| onDatasourceSave(json); |
| onHide(); |
| }) |
| .catch(response => { |
| setIsSaving(false); |
| getClientErrorObject(response).then(({ error }) => { |
| modal.error({ |
| title: 'Error', |
| content: error || t('An error has occurred'), |
| okButtonProps: { danger: true, className: 'btn-danger' }, |
| }); |
| }); |
| }); |
| }; |
| |
| const onDatasourceChange = (data: Record<string, any>, err: Array<any>) => { |
| setCurrentDatasource({ |
| ...data, |
| metrics: data?.metrics.map((metric: Record<string, unknown>) => ({ |
| ...metric, |
| is_certified: metric?.certified_by || metric?.certification_details, |
| })), |
| }); |
| setErrors(err); |
| }; |
| |
| const renderSaveDialog = () => ( |
| <div> |
| <Alert |
| css={theme => ({ |
| 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?')} |
| </div> |
| ); |
| |
| const onClickSave = () => { |
| dialog.current = modal.confirm({ |
| title: t('Confirm save'), |
| content: renderSaveDialog(), |
| onOk: onConfirmSave, |
| icon: null, |
| }); |
| }; |
| |
| return ( |
| <StyledDatasourceModal |
| show={show} |
| onHide={onHide} |
| title={ |
| <span> |
| {t('Edit Dataset ')} |
| <strong>{currentDatasource.table_name}</strong> |
| </span> |
| } |
| footer={ |
| <> |
| {isFeatureEnabled(FeatureFlag.ENABLE_REACT_CRUD_VIEWS) && ( |
| <Button |
| buttonSize="small" |
| buttonStyle="default" |
| data-test="datasource-modal-legacy-edit" |
| className="m-r-5" |
| onClick={() => { |
| window.location.href = |
| currentDatasource.edit_url || currentDatasource.url; |
| }} |
| > |
| {t('Use legacy datasource editor')} |
| </Button> |
| )} |
| <Button |
| data-test="datasource-modal-cancel" |
| buttonSize="small" |
| className="m-r-5" |
| onClick={onHide} |
| > |
| {t('Cancel')} |
| </Button> |
| <Button |
| buttonSize="small" |
| buttonStyle="primary" |
| data-test="datasource-modal-save" |
| onClick={onClickSave} |
| disabled={isSaving || errors.length > 0} |
| > |
| {t('Save')} |
| </Button> |
| </> |
| } |
| responsive |
| > |
| <DatasourceEditor |
| showLoadingForImport |
| height={500} |
| datasource={currentDatasource} |
| onChange={onDatasourceChange} |
| /> |
| {contextHolder} |
| </StyledDatasourceModal> |
| ); |
| }; |
| |
| export default withToasts(DatasourceModal); |