/* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ import React, { useState, useCallback, useEffect, useMemo } from "react"; import { useForm } from "react-hook-form"; import { useHistory, useParams } from "react-router-dom"; import { ChartType, ColumnDataType, CurrencyDataType, Dataset, DatasetType, LocationState, NumberDataType, } from "../models"; import { useWidget, useDashboard, useDatasets, useFullPreview, useChangeBackgroundColor, useScrollUp, } from "../hooks"; import StorageService from "../services/StorageService"; import BackendService from "../services/BackendService"; import Breadcrumbs from "../components/Breadcrumbs"; import VisualizeChart from "../components/VisualizeChart"; import ChooseData from "../components/ChooseData"; import CheckData from "../components/CheckData"; import Spinner from "../components/Spinner"; import UtilsService from "../services/UtilsService"; import "./EditChart.css"; import ColumnsMetadataService from "../services/ColumnsMetadataService"; import DatasetParsingService from "../services/DatasetParsingService"; import PrimaryActionBar from "../components/PrimaryActionBar"; import { useTranslation } from "react-i18next"; import Alert from "../components/Alert"; import ParsingFileService from "../services/ParsingFileService"; interface FormValues { title: string; summary: string; chartType: string; showTitle: boolean; dynamicDatasets: string; staticDatasets: string; summaryBelow: boolean; datasetType: string; sortData: string; horizontalScroll: boolean; stackedChart: boolean; dataLabels: boolean; computePercentages: boolean; showTotal: boolean; significantDigitLabels: boolean; staticFileName: string | undefined; dynamicFileName: string | undefined; } interface PathParams { dashboardId: string; widgetId: string; } function EditChart() { const history = useHistory(); const { state } = history.location; const { t } = useTranslation(); const { dashboardId, widgetId } = useParams(); const { dashboard, loading } = useDashboard(dashboardId); const { dynamicDatasets, staticDatasets, loadingDatasets } = useDatasets(); const { register, errors, handleSubmit, reset, watch } = useForm(); const [dynamicDataset, setDynamicDataset] = useState(undefined); const [staticDataset, setStaticDataset] = useState(undefined); const [csvErrors, setCsvErrors] = useState | undefined>(undefined); const [csvFile, setCsvFile] = useState(undefined); const [fileLoading, setFileLoading] = useState(false); const [datasetLoading, setDatasetLoading] = useState(false); const [editingWidget, setEditingWidget] = useState(false); const [showColumnHeaderAlert, setShowColumnHeaderAlert] = useState(false); const [showNoDatasetTypeAlert, setShowNoDatasetTypeAlert] = useState(false); const [enableContinueButton, setEnableContinueButton] = useState(true); const [step, setStep] = useState(state && state.json ? 1 : 2); const [oldStep, setOldStep] = useState(-1); const [staticFileName, setStaticFileName] = useState(""); const [dynamicFileName, setDynamicFileName] = useState(""); const { widget, datasetType, currentJson, dynamicJson, staticJson, csvJson, setDynamicJson, setCsvJson, } = useWidget(dashboardId, widgetId); const previewPanelId = "preview-chart-panel"; const { fullPreviewButton, fullPreview } = useFullPreview(previewPanelId); const [hiddenColumns, setHiddenColumns] = useState>(new Set()); const [sortByColumn, setSortByColumn] = useState(undefined); const [sortByDesc, setSortByDesc] = useState(undefined); const [dataTypes, setDataTypes] = useState>( new Map(), ); const [numberTypes, setNumberTypes] = useState>( new Map(), ); const [currencyTypes, setCurrencyTypes] = useState>( new Map(), ); const title = watch("title"); const showTitle = watch("showTitle"); const summary = watch("summary"); const summaryBelow = watch("summaryBelow"); const chartType = watch("chartType"); const horizontalScroll = watch("horizontalScroll"); const stackedChart = watch("stackedChart"); const dataLabels = watch("dataLabels"); const computePercentages = watch("computePercentages"); const showTotal = watch("showTotal"); const significantDigitLabels = watch("significantDigitLabels"); const [displayedJson, setDisplayedJson] = useState([]); const [filteredJson, setFilteredJson] = useState([]); const [displayedDatasetType, setDisplayedDatasetType] = useState( undefined, ); const initializeColumnsMetadata = () => { setHiddenColumns(new Set()); setDataTypes(new Map()); setNumberTypes(new Map()); setCurrencyTypes(new Map()); setSortByColumn(undefined); setSortByDesc(false); }; useMemo(() => { const newFilteredJson = DatasetParsingService.getFilteredJson(displayedJson, hiddenColumns); DatasetParsingService.sortFilteredJson(newFilteredJson, sortByColumn, sortByDesc); setFilteredJson(newFilteredJson); }, [displayedJson, hiddenColumns, sortByColumn, sortByDesc]); useEffect(() => { if (widget && dynamicDatasets && staticDatasets && currentJson && datasetType) { const title = widget.content.title; const showTitle = widget.showTitle; const summary = widget.content.summary; const summaryBelow = widget.content.summaryBelow; const chartType = widget.content.chartType; const horizontalScroll = widget.content.horizontalScroll; const stackedChart = widget.content.stackedChart; const dataLabels = widget.content.dataLabels; const computePercentages = widget.content.computePercentages; const showTotal = widget.content.showTotal; if (dynamicDataset) { setDynamicFileName(dynamicDataset?.fileName); } if (staticDataset) { setStaticFileName(staticDataset?.fileName); } reset({ title, showTitle, datasetType: displayedDatasetType ? displayedDatasetType : UtilsService.getDatasetTypeFromState(state, datasetType), summary, summaryBelow, chartType, horizontalScroll, stackedChart, dataLabels, computePercentages, showTotal, significantDigitLabels: widget.content.significantDigitLabels, dynamicDatasets: widget.content.datasetType === DatasetType.DynamicDataset ? widget.content.s3Key.json : "", staticDatasets: widget.content.datasetType === DatasetType.StaticDataset ? widget.content.s3Key.json : "", sortData: UtilsService.getSortData( widget.content.sortByColumn, widget.content.sortByDesc, ), }); if (!displayedDatasetType) { setDisplayedDatasetType( state && state.json && state.staticDataset ? DatasetType.StaticDataset : datasetType, ); if (!displayedJson.length) { setDisplayedJson(state && state.json ? state.json : currentJson); } // Initialize fields related to columns metadata if (widget.content.columnsMetadata && (!state || !state.json)) { const columnsMetadata = widget.content.columnsMetadata; const { hiddenColumns, dataTypes, numberTypes, currencyTypes } = ColumnsMetadataService.parseColumnsMetadata(columnsMetadata); setHiddenColumns(hiddenColumns); setDataTypes(dataTypes); setNumberTypes(numberTypes); setCurrencyTypes(currencyTypes); setSortByColumn(widget.content.sortByColumn); setSortByDesc(widget.content.sortByDesc || false); } } if (!dynamicDataset) { setDynamicDataset( dynamicDatasets.find((d) => d.s3Key.json === widget.content.s3Key.json), ); } if (!staticDataset) { setStaticDataset( state && state.staticDataset ? state.staticDataset : staticDatasets.find((d) => d.s3Key.json === widget.content.s3Key.json), ); } } }, [ widget, dynamicDatasets, staticDatasets, reset, state, currentJson, displayedJson, datasetType, displayedDatasetType, dynamicDataset, staticDataset, ]); const areValidCsvColumnNames = (firstRow: any): boolean => { for (let columnName in firstRow) { if (columnName === "") { return false; } } return true; }; const onFileProcessed = useCallback( async (data: File) => { if (!data) { return; } setDatasetLoading(true); ParsingFileService.parseFile(data, true, (errors: any, results: any) => { if (errors !== null && errors.length) { setCsvErrors(errors); setCsvJson([]); setDisplayedJson([]); return; } initializeColumnsMetadata(); let wrongCSV = !areValidCsvColumnNames(results[0]); if (wrongCSV) { setEnableContinueButton(false); setShowColumnHeaderAlert(true); setCsvFile(undefined); return; } else { setEnableContinueButton(true); setShowColumnHeaderAlert(false); } setShowNoDatasetTypeAlert(false); setCsvErrors(undefined); const csvJson = ParsingFileService.isExcelFile(data.type) ? DatasetParsingService.createHeaderRowJson(results) : results; setCsvJson(csvJson); setDisplayedJson(csvJson); setDatasetLoading(false); }); setCsvFile(data); }, [setDisplayedJson, setCsvJson], ); const uploadDataset = async (): Promise => { if (!csvFile) { // User did not select a new dataset. // No need to upload anything. return null; } if (!csvFile.lastModified) { return { id: widget?.content.datasetId, fileName: csvFile.name, s3Key: widget?.content.s3Key, }; } setFileLoading(true); const uploadResponse = await StorageService.uploadDataset( csvFile, JSON.stringify(displayedJson), t, ); const newDataset = await BackendService.createDataset(csvFile.name, { raw: uploadResponse.s3Keys.raw, json: uploadResponse.s3Keys.json, }); setFileLoading(false); return newDataset; }; const onSubmit = async (values: FormValues) => { if (!widget) { return; } try { let newDataset; if (csvFile) { newDataset = await uploadDataset(); } setEditingWidget(true); await BackendService.editWidget( dashboardId, widgetId, values.title, values.showTitle, { title: values.title, summary: values.summary, summaryBelow: values.summaryBelow, chartType: values.chartType, ...((values.chartType === ChartType.LineChart || values.chartType === ChartType.ColumnChart) && { horizontalScroll: values.horizontalScroll, }), ...((values.chartType === ChartType.BarChart || values.chartType === ChartType.ColumnChart) && { stackedChart: values.stackedChart, }), ...((values.chartType === ChartType.BarChart || values.chartType === ChartType.ColumnChart || values.chartType === ChartType.PieChart || values.chartType === ChartType.DonutChart) && { dataLabels: values.dataLabels, }), ...((values.chartType === ChartType.PieChart || values.chartType === ChartType.DonutChart) && { computePercentages: values.computePercentages, }), ...(values.chartType === ChartType.DonutChart && { showTotal: values.showTotal, }), datasetType: displayedDatasetType, datasetId: newDataset ? newDataset.id : UtilsService.getDatasetPropertyByDatasetType( datasetType, "id", dynamicDataset, staticDataset, ), s3Key: newDataset ? newDataset.s3Key : UtilsService.getDatasetPropertyByDatasetType( datasetType, "s3Key", dynamicDataset, staticDataset, ), fileName: csvFile ? csvFile.name : UtilsService.getDatasetPropertyByDatasetType( datasetType, "fileName", dynamicDataset, staticDataset, ), sortByColumn, sortByDesc, significantDigitLabels: values.significantDigitLabels, columnsMetadata: ColumnsMetadataService.getColumnsMetadata( hiddenColumns, dataTypes, numberTypes, currencyTypes, ), }, widget.updatedAt, ); setEditingWidget(false); history.push(`/admin/dashboard/edit/${dashboardId}`, { alert: { type: "success", message: t("EditChartScreen.EditChartSuccess", { title: values.title, }), }, }); } catch (err) { console.log(t("AddContentFailure"), err); setEditingWidget(false); } }; const onCancel = () => { history.push(`/admin/dashboard/edit/${dashboardId}`); }; const handleChange = async (event: React.FormEvent) => { const target = event.target as HTMLInputElement; if (target.name === "datasetType") { setDatasetLoading(true); const datasetType = target.value as DatasetType; setDisplayedDatasetType(datasetType); initializeColumnsMetadata(); await UtilsService.timeout(0); if (datasetType === DatasetType.DynamicDataset) { setDisplayedJson(dynamicJson); } if (datasetType === DatasetType.StaticDataset) { if (csvJson && csvJson.length) { setDisplayedJson(csvJson); } else { setDisplayedJson(staticJson); } } setDatasetLoading(false); } }; const advanceStep = () => { setStep(step + 1); }; const backStep = () => { setStep(step - 1); }; const goBack = () => { history.push(`/admin/dashboard/${dashboardId}/add-content`); }; const browseDatasets = () => { history.push({ pathname: `/admin/dashboard/${dashboardId}/choose-static-dataset`, state: { redirectUrl: `/admin/dashboard/${dashboardId}/edit-chart/${widgetId}`, crumbLabel: t("EditChartScreen.EditChart"), }, }); }; const selectDynamicDataset = async (selectedDataset: Dataset) => { setDatasetLoading(true); if (selectedDataset && selectedDataset.s3Key && selectedDataset.s3Key.json) { const jsonFile = selectedDataset.s3Key.json; initializeColumnsMetadata(); const dataset = await StorageService.downloadJson(jsonFile); setDynamicDataset(dynamicDatasets.find((d) => d.s3Key.json === jsonFile)); setDynamicJson(dataset); setDisplayedJson(dataset); } setDatasetLoading(false); }; const crumbs = [ { label: t("Dashboards"), url: "/admin/dashboards", }, { label: dashboard?.name, url: `/admin/dashboard/edit/${dashboardId}`, }, ]; if (!loading && widget) { crumbs.push({ label: t("EditChartScreen.EditChart"), url: "", }); } const configHeader = (suffix: string) => (

{t("EditChartScreen.EditChart")}

); useChangeBackgroundColor(); useScrollUp(oldStep, step, setOldStep); return ( <> {loading || loadingDatasets || !widget || !displayedDatasetType || !filteredJson || fileLoading || editingWidget ? ( ) : (
)} ); } export default EditChart;