airbnb/caravel

View on GitHub
superset-frontend/src/dashboard/components/SaveModal.tsx

Summary

Maintainability
A
3 hrs
Test Coverage
/**
 * 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.
 */
/* eslint-env browser */
import { createRef, PureComponent } from 'react';
import { Radio } from 'src/components/Radio';
import { RadioChangeEvent } from 'src/components';
import { Input } from 'src/components/Input';
import Button from 'src/components/Button';
import { t, JsonResponse } from '@superset-ui/core';

import ModalTrigger, { ModalTriggerRef } from 'src/components/ModalTrigger';
import Checkbox from 'src/components/Checkbox';
import {
  SAVE_TYPE_OVERWRITE,
  SAVE_TYPE_NEWDASHBOARD,
} from 'src/dashboard/util/constants';

type SaveType = typeof SAVE_TYPE_OVERWRITE | typeof SAVE_TYPE_NEWDASHBOARD;

type SaveModalProps = {
  addSuccessToast: (arg: string) => void;
  addDangerToast: (arg: string) => void;
  dashboardId: number;
  dashboardTitle: string;
  dashboardInfo: Record<string, any>;
  expandedSlices: Record<string, any>;
  layout: Record<string, any>;
  saveType: SaveType;
  triggerNode: JSX.Element;
  customCss: string;
  colorNamespace?: string;
  colorScheme?: string;
  onSave: (data: any, id: number | string, saveType: SaveType) => void;
  canOverwrite: boolean;
  shouldPersistRefreshFrequency: boolean;
  refreshFrequency: number;
  lastModifiedTime: number;
};

type SaveModalState = {
  saveType: SaveType;
  newDashName: string;
  duplicateSlices: boolean;
};

const defaultProps = {
  saveType: SAVE_TYPE_OVERWRITE,
  colorNamespace: undefined,
  colorScheme: undefined,
  shouldPersistRefreshFrequency: false,
};

class SaveModal extends PureComponent<SaveModalProps, SaveModalState> {
  static defaultProps = defaultProps;

  modal: ModalTriggerRef | null;

  onSave: (
    data: Record<string, any>,
    dashboardId: number | string,
    saveType: SaveType,
  ) => Promise<JsonResponse>;

  constructor(props: SaveModalProps) {
    super(props);
    this.state = {
      saveType: props.saveType,
      newDashName: `${props.dashboardTitle} ${t('[copy]')}`,
      duplicateSlices: false,
    };

    this.handleSaveTypeChange = this.handleSaveTypeChange.bind(this);
    this.handleNameChange = this.handleNameChange.bind(this);
    this.saveDashboard = this.saveDashboard.bind(this);
    this.toggleDuplicateSlices = this.toggleDuplicateSlices.bind(this);
    this.onSave = this.props.onSave.bind(this);
    this.modal = createRef() as ModalTriggerRef;
  }

  toggleDuplicateSlices(): void {
    this.setState(prevState => ({
      duplicateSlices: !prevState.duplicateSlices,
    }));
  }

  handleSaveTypeChange(event: RadioChangeEvent) {
    this.setState({
      saveType: (event.target as HTMLInputElement).value as SaveType,
    });
  }

  handleNameChange(name: string) {
    this.setState({
      newDashName: name,
      saveType: SAVE_TYPE_NEWDASHBOARD,
    });
  }

  saveDashboard() {
    const { saveType, newDashName } = this.state;
    const {
      dashboardTitle,
      dashboardInfo,
      layout: positions,
      customCss,
      dashboardId,
      refreshFrequency: currentRefreshFrequency,
      shouldPersistRefreshFrequency,
      lastModifiedTime,
    } = this.props;

    // check refresh frequency is for current session or persist
    const refreshFrequency = shouldPersistRefreshFrequency
      ? currentRefreshFrequency
      : dashboardInfo.metadata?.refresh_frequency; // eslint-disable camelcase

    const data = {
      certified_by: dashboardInfo.certified_by,
      certification_details: dashboardInfo.certification_details,
      css: customCss,
      dashboard_title:
        saveType === SAVE_TYPE_NEWDASHBOARD ? newDashName : dashboardTitle,
      duplicate_slices: this.state.duplicateSlices,
      last_modified_time: lastModifiedTime,
      owners: dashboardInfo.owners,
      roles: dashboardInfo.roles,
      metadata: {
        ...dashboardInfo?.metadata,
        positions,
        refresh_frequency: refreshFrequency,
      },
    };

    if (saveType === SAVE_TYPE_NEWDASHBOARD && !newDashName) {
      this.props.addDangerToast(
        t('You must pick a name for the new dashboard'),
      );
    } else {
      this.onSave(data, dashboardId, saveType).then((resp: JsonResponse) => {
        if (saveType === SAVE_TYPE_NEWDASHBOARD && resp.json?.result?.id) {
          window.location.href = `/superset/dashboard/${resp.json.result.id}/`;
        }
      });
      this.modal?.current?.close?.();
    }
  }

  render() {
    return (
      <ModalTrigger
        ref={this.modal}
        triggerNode={this.props.triggerNode}
        modalTitle={t('Save dashboard')}
        modalBody={
          <div>
            <Radio
              value={SAVE_TYPE_OVERWRITE}
              onChange={this.handleSaveTypeChange}
              checked={this.state.saveType === SAVE_TYPE_OVERWRITE}
              disabled={!this.props.canOverwrite}
            >
              {t('Overwrite Dashboard [%s]', this.props.dashboardTitle)}
            </Radio>
            <hr />
            <Radio
              value={SAVE_TYPE_NEWDASHBOARD}
              onChange={this.handleSaveTypeChange}
              checked={this.state.saveType === SAVE_TYPE_NEWDASHBOARD}
            >
              {t('Save as:')}
            </Radio>
            <Input
              type="text"
              placeholder={t('[dashboard name]')}
              value={this.state.newDashName}
              onFocus={e => this.handleNameChange(e.target.value)}
              onChange={e => this.handleNameChange(e.target.value)}
            />
            <div className="m-l-25 m-t-5">
              <Checkbox
                checked={this.state.duplicateSlices}
                onChange={() => this.toggleDuplicateSlices()}
              />
              <span className="m-l-5">{t('also copy (duplicate) charts')}</span>
            </div>
          </div>
        }
        modalFooter={
          <div>
            <Button
              data-test="modal-save-dashboard-button"
              buttonStyle="primary"
              onClick={this.saveDashboard}
            >
              {t('Save')}
            </Button>
          </div>
        }
      />
    );
  }
}

export default SaveModal;