2022-09-02 09:16:30 +00:00
import { get } from "lodash" ;
2020-03-13 12:06:41 +00:00
import React from "react" ;
2020-03-20 04:02:49 +00:00
import styled from "styled-components" ;
2023-07-28 13:29:16 +00:00
import * as echarts from "echarts" ;
2022-05-04 09:45:57 +00:00
import { invisible } from "constants/DefaultTheme" ;
2022-01-07 06:08:17 +00:00
import { getAppsmithConfigs } from "@appsmith/configs" ;
chore: upgrade to prettier v2 + enforce import types (#21013)Co-authored-by: Satish Gandham <hello@satishgandham.com> Co-authored-by: Satish Gandham <satish.iitg@gmail.com>
## Description
This PR upgrades Prettier to v2 + enforces TypeScript’s [`import
type`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#type-only-imports-and-export)
syntax where applicable. It’s submitted as a separate PR so we can merge
it easily.
As a part of this PR, we reformat the codebase heavily:
- add `import type` everywhere where it’s required, and
- re-format the code to account for Prettier 2’s breaking changes:
https://prettier.io/blog/2020/03/21/2.0.0.html#breaking-changes
This PR is submitted against `release` to make sure all new code by team
members will adhere to new formatting standards, and we’ll have fewer
conflicts when merging `bundle-optimizations` into `release`. (I’ll
merge `release` back into `bundle-optimizations` once this PR is
merged.)
### Why is this needed?
This PR is needed because, for the Lodash optimization from
https://github.com/appsmithorg/appsmith/commit/7cbb12af886621256224be0c93e6a465dd710ad3,
we need to use `import type`. Otherwise, `babel-plugin-lodash` complains
that `LoDashStatic` is not a lodash function.
However, just using `import type` in the current codebase will give you
this:
<img width="962" alt="Screenshot 2023-03-08 at 17 45 59"
src="https://user-images.githubusercontent.com/2953267/223775744-407afa0c-e8b9-44a1-90f9-b879348da57f.png">
That’s because Prettier 1 can’t parse `import type` at all. To parse it,
we need to upgrade to Prettier 2.
### Why enforce `import type`?
Apart from just enabling `import type` support, this PR enforces
specifying `import type` everywhere it’s needed. (Developers will get
immediate TypeScript and ESLint errors when they forget to do so.)
I’m doing this because I believe `import type` improves DX and makes
refactorings easier.
Let’s say you had a few imports like below. Can you tell which of these
imports will increase the bundle size? (Tip: it’s not all of them!)
```ts
// app/client/src/workers/Linting/utils.ts
import { Position } from "codemirror";
import { LintError as JSHintError, LintOptions } from "jshint";
import { get, isEmpty, isNumber, keys, last, set } from "lodash";
```
It’s pretty hard, right?
What about now?
```ts
// app/client/src/workers/Linting/utils.ts
import type { Position } from "codemirror";
import type { LintError as JSHintError, LintOptions } from "jshint";
import { get, isEmpty, isNumber, keys, last, set } from "lodash";
```
Now, it’s clear that only `lodash` will be bundled.
This helps developers to see which imports are problematic, but it
_also_ helps with refactorings. Now, if you want to see where
`codemirror` is bundled, you can just grep for `import \{.*\} from
"codemirror"` – and you won’t get any type-only imports.
This also helps (some) bundlers. Upon transpiling, TypeScript erases
type-only imports completely. In some environment (not ours), this makes
the bundle smaller, as the bundler doesn’t need to bundle type-only
imports anymore.
## Type of change
- Chore (housekeeping or task changes that don't impact user perception)
## How Has This Been Tested?
This was tested to not break the build.
### Test Plan
> Add Testsmith test cases links that relate to this PR
### Issues raised during DP testing
> Link issues raised during DP testing for better visiblity and tracking
(copy link from comments dropped on this PR)
## Checklist:
### Dev activity
- [x] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] PR is being merged under a feature flag
### QA activity:
- [ ] Test plan has been approved by relevant developers
- [ ] Test plan has been peer reviewed by QA
- [ ] Cypress test cases have been added and approved by either SDET or
manual QA
- [ ] Organized project review call with relevant stakeholders after
Round 1/2 of QA
- [ ] Added Test Plan Approved label after reveiwing all Cypress test
---------
Co-authored-by: Satish Gandham <hello@satishgandham.com>
Co-authored-by: Satish Gandham <satish.iitg@gmail.com>
2023-03-16 11:41:47 +00:00
import type {
2021-09-09 15:10:22 +00:00
ChartType ,
CustomFusionChartConfig ,
AllChartData ,
ChartSelectedDataPoint ,
2021-08-26 09:58:43 +00:00
LabelOrientation ,
2021-09-09 15:10:22 +00:00
} from "../constants" ;
2023-07-28 13:29:16 +00:00
2021-09-09 15:10:22 +00:00
import log from "loglevel" ;
2023-07-28 13:29:16 +00:00
import equal from "fast-deep-equal/es6" ;
import type { WidgetPositionProps } from "widgets/BaseWidget" ;
import { ChartErrorComponent } from "./ChartErrorComponent" ;
import { EChartsConfigurationBuilder } from "./EChartsConfigurationBuilder" ;
import { EChartsDatasetBuilder } from "./EChartsDatasetBuilder" ;
2022-02-28 09:23:19 +00:00
// Leaving this require here. Ref: https://stackoverflow.com/questions/41292559/could-not-find-a-declaration-file-for-module-module-name-path-to-module-nam/42505940#42505940
// FusionCharts comes with its own typings so there is no need to separately import them. But an import from fusioncharts/core still requires a declaration file.
2020-03-20 04:02:49 +00:00
const FusionCharts = require ( "fusioncharts" ) ;
2021-03-24 22:05:04 +00:00
const plugins : Record < string , any > = {
Charts : require ( "fusioncharts/fusioncharts.charts" ) ,
FusionTheme : require ( "fusioncharts/themes/fusioncharts.theme.fusion" ) ,
Widgets : require ( "fusioncharts/fusioncharts.widgets" ) ,
ZoomScatter : require ( "fusioncharts/fusioncharts.zoomscatter" ) ,
ZoomLine : require ( "fusioncharts/fusioncharts.zoomline" ) ,
PowerCharts : require ( "fusioncharts/fusioncharts.powercharts" ) ,
TimeSeries : require ( "fusioncharts/fusioncharts.timeseries" ) ,
OverlappedColumn : require ( "fusioncharts/fusioncharts.overlappedcolumn2d" ) ,
OverlappedBar : require ( "fusioncharts/fusioncharts.overlappedbar2d" ) ,
TreeMap : require ( "fusioncharts/fusioncharts.treemap" ) ,
Maps : require ( "fusioncharts/fusioncharts.maps" ) ,
Gantt : require ( "fusioncharts/fusioncharts.gantt" ) ,
VML : require ( "fusioncharts/fusioncharts.vml" ) ,
} ;
2020-12-02 10:42:51 +00:00
2021-03-24 22:05:04 +00:00
// Enable all plugins.
// This is needed to support custom chart configs
Object . keys ( plugins ) . forEach ( ( key : string ) = >
( plugins [ key ] as any ) ( FusionCharts ) ,
) ;
2020-12-02 10:42:51 +00:00
2021-03-24 22:05:04 +00:00
const { fusioncharts } = getAppsmithConfigs ( ) ;
2020-12-02 10:42:51 +00:00
FusionCharts . options . license ( {
key : fusioncharts.licenseKey ,
creditLabel : false ,
} ) ;
2020-03-13 12:06:41 +00:00
2023-07-28 13:29:16 +00:00
export interface ChartComponentState {
eChartsError : Error | undefined ;
chartType : ChartType ;
}
export interface ChartComponentProps extends WidgetPositionProps {
2021-10-25 11:39:39 +00:00
allowScroll : boolean ;
2021-04-26 10:35:59 +00:00
chartData : AllChartData ;
2020-03-13 12:06:41 +00:00
chartName : string ;
2021-08-26 09:58:43 +00:00
chartType : ChartType ;
customFusionChartConfig : CustomFusionChartConfig ;
2023-04-14 13:55:44 +00:00
hasOnDataPointClick : boolean ;
2020-03-13 12:06:41 +00:00
isVisible? : boolean ;
2022-01-18 07:51:28 +00:00
isLoading : boolean ;
2021-09-06 12:15:58 +00:00
setAdaptiveYMin : boolean ;
2021-08-26 09:58:43 +00:00
labelOrientation? : LabelOrientation ;
2021-08-18 07:11:07 +00:00
onDataPointClick : ( selectedDataPoint : ChartSelectedDataPoint ) = > void ;
2021-08-26 09:58:43 +00:00
widgetId : string ;
xAxisName : string ;
yAxisName : string ;
2022-05-04 09:45:57 +00:00
borderRadius : string ;
boxShadow? : string ;
primaryColor? : string ;
2022-08-12 12:10:17 +00:00
fontFamily? : string ;
2023-07-28 13:29:16 +00:00
dimensions : {
componentWidth : number ;
componentHeight : number ;
} ;
2020-03-13 12:06:41 +00:00
}
2023-07-28 13:29:16 +00:00
const ChartsContainer = styled . div `
position : relative ;
height : 100 % ;
width : 100 % ;
` ;
2021-03-04 05:24:47 +00:00
const CanvasContainer = styled . div <
2023-04-14 13:55:44 +00:00
Omit < ChartComponentProps , " onDataPointClick " | " hasOnDataPointClick " >
2021-03-04 05:24:47 +00:00
> `
2022-05-04 09:45:57 +00:00
border - radius : $ { ( { borderRadius } ) = > borderRadius } ;
box - shadow : $ { ( { boxShadow } ) = > ` ${ boxShadow } ` } ! important ;
2020-03-13 12:06:41 +00:00
height : 100 % ;
width : 100 % ;
2023-07-28 13:29:16 +00:00
background : var ( -- ads - v2 - color - bg ) ;
2021-02-08 07:30:01 +00:00
overflow : hidden ;
2020-03-13 12:06:41 +00:00
position : relative ;
2020-12-24 04:32:25 +00:00
$ { ( props ) = > ( ! props . isVisible ? invisible : "" ) } ;
2020-04-15 11:42:11 +00:00
padding : 10px 0 0 0 ;
2020-03-13 12:06:41 +00:00
} ` ;
2023-07-28 13:29:16 +00:00
class ChartComponent extends React . Component <
ChartComponentProps ,
ChartComponentState
> {
fusionChartsInstance : any = null ;
echartsInstance : echarts.ECharts | undefined ;
2020-03-13 12:06:41 +00:00
2023-07-28 13:29:16 +00:00
customFusionChartContainerId =
this . props . widgetId + "custom-fusion-chart-container" ;
eChartsContainerId = this . props . widgetId + "echart-container" ;
eChartsHTMLContainer : HTMLElement | null = null ;
2021-02-26 05:28:31 +00:00
2023-07-28 13:29:16 +00:00
eChartsData : AllChartData = { } ;
echartsConfigurationBuilder : EChartsConfigurationBuilder ;
2021-02-16 10:29:08 +00:00
2023-07-28 13:29:16 +00:00
echartConfiguration : Record < string , any > = { } ;
2021-04-26 10:35:59 +00:00
2023-07-28 13:29:16 +00:00
constructor ( props : ChartComponentProps ) {
super ( props ) ;
this . echartsConfigurationBuilder = new EChartsConfigurationBuilder ( ) ;
2021-04-26 10:35:59 +00:00
2023-07-28 13:29:16 +00:00
this . state = {
eChartsError : undefined ,
chartType : this.props.chartType ,
} ;
}
2021-04-26 10:35:59 +00:00
2023-07-28 13:29:16 +00:00
getEChartsOptions = ( ) = > {
const options = {
. . . this . echartsConfigurationBuilder . prepareEChartConfig (
this . props ,
this . eChartsData ,
) ,
dataset : {
. . . EChartsDatasetBuilder . datasetFromData ( this . eChartsData ) ,
} ,
} ;
return options ;
2020-03-13 12:06:41 +00:00
} ;
2023-07-28 13:29:16 +00:00
dataClickCallback = ( params : echarts.ECElementEvent ) = > {
const eventData : unknown [ ] = params . data as unknown [ ] ;
const x : unknown = eventData [ 0 ] ;
2021-04-26 10:35:59 +00:00
2023-07-28 13:29:16 +00:00
const index = ( params . seriesIndex ? ? 0 ) + 1 ;
const y : unknown = eventData [ index ] ;
2021-04-26 10:35:59 +00:00
2023-07-28 13:29:16 +00:00
const seriesName =
params . seriesName && params . seriesName ? . length > 0
? params . seriesName
: "null" ;
2021-04-26 10:35:59 +00:00
2023-07-28 13:29:16 +00:00
this . props . onDataPointClick ( {
x : x ,
y : y ,
seriesTitle : seriesName ,
2021-04-26 10:35:59 +00:00
} ) ;
2020-04-15 11:42:11 +00:00
} ;
2023-07-28 13:29:16 +00:00
initializeEchartsInstance = ( ) = > {
this . eChartsHTMLContainer = document . getElementById (
this . eChartsContainerId ,
) ;
if ( ! this . eChartsHTMLContainer ) {
return ;
}
2021-04-26 10:35:59 +00:00
2023-07-28 13:29:16 +00:00
if ( ! this . echartsInstance || this . echartsInstance . isDisposed ( ) ) {
this . echartsInstance = echarts . init (
this . eChartsHTMLContainer ,
undefined ,
2021-02-26 05:28:31 +00:00
{
2023-07-28 13:29:16 +00:00
renderer : "svg" ,
2021-02-26 05:28:31 +00:00
} ,
2023-07-28 13:29:16 +00:00
) ;
2020-04-29 10:29:02 +00:00
}
2020-04-15 11:42:11 +00:00
} ;
2023-08-03 05:58:37 +00:00
shouldResizeECharts = ( ) = > {
return (
this . echartsInstance ? . getHeight ( ) !=
this . props . dimensions . componentHeight ||
this . echartsInstance ? . getWidth ( ) != this . props . dimensions . componentWidth
) ;
2021-08-26 09:58:43 +00:00
} ;
2023-07-28 13:29:16 +00:00
renderECharts = ( ) = > {
this . initializeEchartsInstance ( ) ;
2022-08-12 12:10:17 +00:00
2023-07-28 13:29:16 +00:00
if ( ! this . echartsInstance ) {
return ;
2021-08-26 09:58:43 +00:00
}
2023-07-28 13:29:16 +00:00
const newConfiguration = this . getEChartsOptions ( ) ;
2023-08-03 05:58:37 +00:00
const needsNewConfig = ! equal ( newConfiguration , this . echartConfiguration ) ;
const resizedNeeded = this . shouldResizeECharts ( ) ;
2021-08-18 07:11:07 +00:00
2023-08-03 05:58:37 +00:00
if ( needsNewConfig ) {
this . echartConfiguration = newConfiguration ;
2023-07-28 13:29:16 +00:00
this . echartsInstance . off ( "click" ) ;
this . echartsInstance . on ( "click" , this . dataClickCallback ) ;
2021-04-26 10:35:59 +00:00
2023-08-03 05:58:37 +00:00
try {
2023-07-28 13:29:16 +00:00
this . echartsInstance . setOption ( this . echartConfiguration , true ) ;
2023-08-03 05:58:37 +00:00
2023-07-28 13:29:16 +00:00
if ( this . state . eChartsError ) {
this . setState ( { eChartsError : undefined } ) ;
}
2023-08-03 05:58:37 +00:00
} catch ( error ) {
this . disposeECharts ( ) ;
this . setState ( { eChartsError : error as Error } ) ;
2023-07-28 13:29:16 +00:00
}
2023-08-03 05:58:37 +00:00
}
2023-07-28 13:29:16 +00:00
2023-08-03 05:58:37 +00:00
if ( resizedNeeded ) {
this . echartsInstance . resize ( {
width : this.props.dimensions.componentWidth ,
height : this.props.dimensions.componentHeight ,
} ) ;
2020-04-15 11:42:11 +00:00
}
} ;
2023-07-28 13:29:16 +00:00
disposeECharts = ( ) = > {
this . echartsInstance ? . dispose ( ) ;
2021-03-24 22:05:04 +00:00
} ;
2023-07-28 13:29:16 +00:00
componentDidMount() {
this . eChartsData = EChartsDatasetBuilder . chartData ( this . props ) ;
this . renderChartingLibrary ( ) ;
}
2021-02-26 05:28:31 +00:00
2023-07-28 13:29:16 +00:00
componentWillUnmount() {
this . disposeECharts ( ) ;
this . disposeFusionCharts ( ) ;
}
2020-04-15 11:42:11 +00:00
2023-07-28 13:29:16 +00:00
renderChartingLibrary() {
if ( this . state . chartType === "CUSTOM_FUSION_CHART" ) {
this . disposeECharts ( ) ;
this . renderFusionCharts ( ) ;
} else {
this . disposeFusionCharts ( ) ;
this . initializeEchartsInstance ( ) ;
this . renderECharts ( ) ;
}
}
componentDidUpdate() {
2022-08-23 13:32:57 +00:00
if (
2023-07-28 13:29:16 +00:00
this . props . chartType == "CUSTOM_FUSION_CHART" &&
this . state . chartType != "CUSTOM_FUSION_CHART"
) {
this . setState ( {
eChartsError : undefined ,
chartType : "CUSTOM_FUSION_CHART" ,
} ) ;
} else if (
this . props . chartType != "CUSTOM_FUSION_CHART" &&
this . state . chartType === "CUSTOM_FUSION_CHART"
2022-08-23 13:32:57 +00:00
) {
2023-07-28 13:29:16 +00:00
// User has selected one of the ECharts option
this . setState ( { chartType : "AREA_CHART" } ) ;
} else {
this . eChartsData = EChartsDatasetBuilder . chartData ( this . props ) ;
this . renderChartingLibrary ( ) ;
2021-08-18 07:11:07 +00:00
}
2023-07-28 13:29:16 +00:00
}
disposeFusionCharts = ( ) = > {
this . fusionChartsInstance = null ;
2021-08-18 07:11:07 +00:00
} ;
2023-07-28 13:29:16 +00:00
renderFusionCharts = ( ) = > {
if ( this . fusionChartsInstance ) {
const { dataSource , type } = this . getCustomFusionChartDataSource ( ) ;
this . fusionChartsInstance . chartType ( type ) ;
this . fusionChartsInstance . setChartData ( dataSource ) ;
} else {
const config = this . customFusionChartConfig ( ) ;
this . fusionChartsInstance = new FusionCharts ( config ) ;
FusionCharts . ready ( ( ) = > {
/ * C o m p o n e n t c o u l d b e u n m o u n t e d b e f o r e F u s i o n C h a r t s i s r e a d y ,
this check ensure we don ' t render on unmounted component * /
if ( this . fusionChartsInstance ) {
try {
this . fusionChartsInstance . render ( ) ;
} catch ( e ) {
log . error ( e ) ;
}
}
} ) ;
2021-03-24 22:05:04 +00:00
}
2023-07-28 13:29:16 +00:00
} ;
2021-02-26 05:28:31 +00:00
2023-07-28 13:29:16 +00:00
customFusionChartConfig() {
2020-03-20 04:02:49 +00:00
const chartConfig = {
2023-07-28 13:29:16 +00:00
renderAt : this.customFusionChartContainerId ,
2020-03-20 04:02:49 +00:00
width : "100%" ,
height : "100%" ,
2021-02-22 16:31:13 +00:00
events : {
dataPlotClick : ( evt : any ) = > {
const data = evt . data ;
2023-07-28 13:29:16 +00:00
const seriesTitle = get ( data , "datasetName" , "" ) ;
2021-02-22 16:31:13 +00:00
this . props . onDataPointClick ( {
x : data.categoryLabel ,
y : data.dataValue ,
2021-08-18 07:11:07 +00:00
seriesTitle ,
2021-02-22 16:31:13 +00:00
} ) ;
} ,
} ,
2023-07-28 13:29:16 +00:00
. . . this . getCustomFusionChartDataSource ( ) ,
2020-03-20 04:02:49 +00:00
} ;
2023-07-28 13:29:16 +00:00
return chartConfig ;
2020-03-20 04:02:49 +00:00
}
2023-07-28 13:29:16 +00:00
getCustomFusionChartDataSource = ( ) = > {
// in case of evaluation error, customFusionChartConfig can be undefined
let config = this . props . customFusionChartConfig as CustomFusionChartConfig ;
2020-08-28 09:52:18 +00:00
2023-07-28 13:29:16 +00:00
if ( config && config . dataSource ) {
config = {
. . . config ,
dataSource : {
chart : {
. . . config . dataSource . chart ,
caption : this.props.chartName || config . dataSource . chart . caption ,
setAdaptiveYMin : this.props.setAdaptiveYMin ? "1" : "0" ,
} ,
. . . config . dataSource ,
} ,
} ;
2020-03-20 04:02:49 +00:00
}
2023-07-28 13:29:16 +00:00
return config || { } ;
} ;
2020-03-20 04:02:49 +00:00
render() {
2021-03-04 05:24:47 +00:00
//eslint-disable-next-line @typescript-eslint/no-unused-vars
2023-04-14 13:55:44 +00:00
const { hasOnDataPointClick , onDataPointClick , . . . rest } = this . props ;
// Avoid propagating the click events to upwards
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const onClick = hasOnDataPointClick
? ( e : React.MouseEvent < HTMLDivElement , MouseEvent > ) = > e . stopPropagation ( )
: undefined ;
2022-01-18 07:51:28 +00:00
return (
< CanvasContainer
className = { this . props . isLoading ? "bp3-skeleton" : "" }
2023-04-14 13:55:44 +00:00
onClick = { onClick }
2022-01-18 07:51:28 +00:00
{ . . . rest }
2023-07-28 13:29:16 +00:00
>
{ this . state . chartType !== "CUSTOM_FUSION_CHART" && (
< ChartsContainer id = { this . eChartsContainerId } / >
) }
{ this . state . chartType === "CUSTOM_FUSION_CHART" && (
< ChartsContainer id = { this . customFusionChartContainerId } / >
) }
{ this . state . eChartsError && (
< ChartErrorComponent error = { this . state . eChartsError } / >
) }
< / CanvasContainer >
2022-01-18 07:51:28 +00:00
) ;
2020-03-20 04:02:49 +00:00
}
}
2020-03-13 12:06:41 +00:00
export default ChartComponent ;