2020-10-06 09:01:51 +00:00
import React from "react" ;
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 { WidgetProps } from "./BaseWidget" ;
2023-03-30 04:54:29 +00:00
import { debounce , fromPairs , isEmpty } from "lodash" ;
2022-01-28 11:10:05 +00:00
import { EditorContext } from "components/editorComponents/EditorContextProvider" ;
2021-04-23 13:50:55 +00:00
import AppsmithConsole from "utils/AppsmithConsole" ;
2024-08-06 14:52:22 +00:00
import { ENTITY_TYPE } from "ee/entities/AppsmithConsole/utils" ;
2021-06-21 08:20:25 +00:00
import LOG_TYPE from "entities/AppsmithConsole/logtype" ;
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 { ExecuteTriggerPayload } from "constants/AppsmithActionConstants/ActionConstants" ;
2022-05-25 09:46:14 +00:00
import { connect } from "react-redux" ;
import { getWidgetMetaProps } from "sagas/selectors" ;
2024-08-06 14:52:22 +00:00
import type { AppState } from "ee/reducers" ;
2023-03-30 04:54:29 +00:00
import { error } from "loglevel" ;
2023-09-06 12:15:04 +00:00
import WidgetFactory from "WidgetProvider/factory" ;
2023-10-04 11:53:29 +00:00
import type BaseWidget from "./BaseWidget" ;
2023-03-30 04:54:29 +00:00
export type pushAction = (
propertyName : string | batchUpdateWidgetMetaPropertyType ,
propertyValue? : unknown ,
actionExecution? : DebouncedExecuteActionPayload ,
) = > void ;
2020-10-06 16:47:16 +00:00
2022-04-08 06:09:23 +00:00
export type DebouncedExecuteActionPayload = Omit <
2021-09-15 05:11:13 +00:00
ExecuteTriggerPayload ,
2020-10-06 16:47:16 +00:00
"dynamicString"
2020-10-21 04:25:32 +00:00
> & {
dynamicString? : string ;
} ;
2023-03-30 04:54:29 +00:00
export type batchUpdateWidgetMetaPropertyType = {
propertyName : string ;
propertyValue : unknown ;
actionExecution? : DebouncedExecuteActionPayload ;
} [ ] ;
2020-10-06 09:01:51 +00:00
export interface WithMeta {
2023-03-30 04:54:29 +00:00
commitBatchMetaUpdates : ( ) = > void ;
pushBatchMetaUpdates : pushAction ;
2020-10-06 16:47:16 +00:00
updateWidgetMetaProperty : (
propertyName : string ,
2022-02-25 20:46:04 +00:00
propertyValue : unknown ,
2020-10-06 16:47:16 +00:00
actionExecution? : DebouncedExecuteActionPayload ,
) = > void ;
2020-10-06 09:01:51 +00:00
}
2023-10-11 07:35:24 +00:00
interface WidgetMetaProps {
metaState : Record < string , unknown > ;
}
2022-05-25 09:46:14 +00:00
type metaHOCProps = WidgetProps & WidgetMetaProps ;
2022-02-25 20:46:04 +00:00
2022-05-25 09:46:14 +00:00
function withMeta ( WrappedWidget : typeof BaseWidget ) {
class MetaHOC extends React . PureComponent < metaHOCProps > {
2020-10-06 09:01:51 +00:00
static contextType = EditorContext ;
2022-05-25 09:46:14 +00:00
context ! : React . ContextType < typeof EditorContext > ;
2020-10-06 09:01:51 +00:00
2022-02-25 20:46:04 +00:00
initialMetaState : Record < string , unknown > ;
2022-05-25 09:46:14 +00:00
actionsToExecute : Record < string , DebouncedExecuteActionPayload > ;
2023-03-30 04:54:29 +00:00
batchMetaUpdates : batchUpdateWidgetMetaPropertyType ;
2022-05-25 09:46:14 +00:00
updatedProperties : Record < string , boolean > ;
constructor ( props : metaHOCProps ) {
2020-10-06 09:01:51 +00:00
super ( props ) ;
2023-09-06 12:15:04 +00:00
const metaProperties = WidgetFactory . getWidgetMetaPropertiesMap (
WrappedWidget . type ,
) ;
2022-02-25 20:46:04 +00:00
this . initialMetaState = fromPairs (
2023-04-18 07:10:36 +00:00
Object . entries ( metaProperties ) . map ( ( [ key , value ] ) = > {
return [ key , value ] ;
2020-10-06 09:01:51 +00:00
} ) ,
) ;
2022-05-25 09:46:14 +00:00
this . updatedProperties = { } ;
this . actionsToExecute = { } ;
2023-03-30 04:54:29 +00:00
this . batchMetaUpdates = [ ] ;
2020-10-06 09:01:51 +00:00
}
2022-05-25 09:46:14 +00:00
addPropertyForEval = (
propertyName : string ,
actionExecution? : DebouncedExecuteActionPayload ,
) = > {
// Add meta updates in updatedProperties to push to evaluation
this . updatedProperties [ propertyName ] = true ;
if ( actionExecution ) {
// Adding action inside actionsToExecute
this . actionsToExecute [ propertyName ] = actionExecution ;
2022-02-25 20:46:04 +00:00
}
2022-05-25 09:46:14 +00:00
} ;
2022-02-25 20:46:04 +00:00
2022-05-25 09:46:14 +00:00
removeBatchActions = ( propertyName : string ) = > {
delete this . actionsToExecute [ propertyName ] ;
} ;
runBatchActions = ( ) = > {
const { executeAction } = this . context ;
const batchActionsToRun = Object . entries ( this . actionsToExecute ) ;
batchActionsToRun . map ( ( [ propertyName , actionExecution ] ) = > {
if ( actionExecution && actionExecution . dynamicString && executeAction ) {
executeAction ( {
. . . actionExecution ,
dynamicString : actionExecution.dynamicString , // when we spread the object above check of dynamic string doesn't account for type.
source : {
id : this.props.widgetId ,
name : this.props.widgetName ,
} ,
} ) ;
// remove action from batch
this . removeBatchActions ( propertyName ) ;
actionExecution . triggerPropertyName &&
AppsmithConsole . info ( {
text : ` ${ actionExecution . triggerPropertyName } triggered ` ,
source : {
type : ENTITY_TYPE . WIDGET ,
id : this.props.widgetId ,
name : this.props.widgetName ,
} ,
} ) ;
2020-10-06 09:01:51 +00:00
}
} ) ;
2022-05-25 09:46:14 +00:00
} ;
handleTriggerEvalOnMetaUpdate = ( ) = > {
const { triggerEvalOnMetaUpdate } = this . context ;
// if we have meta property update which needs to be send to evaluation only then trigger evaluation.
// this will avoid triggering evaluation for the trailing end of debounce, when there are no meta updates.
if ( Object . keys ( this . updatedProperties ) . length ) {
if ( triggerEvalOnMetaUpdate ) triggerEvalOnMetaUpdate ( ) ;
this . updatedProperties = { } ; // once we trigger evaluation, we remove those property from updatedProperties
}
this . runBatchActions ( ) ;
} ;
debouncedTriggerEvalOnMetaUpdate = debounce (
this . handleTriggerEvalOnMetaUpdate ,
200 ,
{
leading : true ,
trailing : true ,
} ,
) ;
2020-10-06 09:01:51 +00:00
updateWidgetMetaProperty = (
propertyName : string ,
2022-02-25 20:46:04 +00:00
propertyValue : unknown ,
2020-10-06 16:47:16 +00:00
actionExecution? : DebouncedExecuteActionPayload ,
2020-10-06 09:01:51 +00:00
) : void = > {
2022-05-25 09:46:14 +00:00
this . handleUpdateWidgetMetaProperty (
propertyName ,
propertyValue ,
actionExecution ,
2020-10-06 16:47:16 +00:00
) ;
2020-10-06 09:01:51 +00:00
} ;
2023-03-30 04:54:29 +00:00
/ * *
This function pushes meta updates that can be commited later .
If there are multiple updates , use this function to batch those updates together .
* /
pushBatchMetaUpdates : pushAction = ( firstArgument , . . . restArgs ) = > {
//if first argument is an array its a batch lets push it
if ( Array . isArray ( firstArgument ) ) {
this . batchMetaUpdates . push ( . . . firstArgument ) ;
return ;
}
//if first argument is a string its a propertyName arg and we are pushing a single action
if ( typeof firstArgument === "string" ) {
const [ propertyValue , actionExecution ] = restArgs ;
this . batchMetaUpdates . push ( {
propertyName : firstArgument ,
propertyValue ,
actionExecution ,
} ) ;
return ;
}
const allArgs = [ firstArgument , . . . restArgs ] ;
error ( "unknown args " , allArgs ) ;
} ;
/ * *
This function commits all batched updates in one go .
* /
commitBatchMetaUpdates = ( ) = > {
//ignore commit if batch array is empty
if ( ! this . batchMetaUpdates || ! this . batchMetaUpdates . length ) return ;
const metaUpdates = this . batchMetaUpdates . reduce (
2024-07-31 15:41:28 +00:00
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2023-03-30 04:54:29 +00:00
( acc : any , { propertyName , propertyValue } ) = > {
acc [ propertyName ] = propertyValue ;
return acc ;
} ,
{ } ,
) ;
AppsmithConsole . info ( {
logType : LOG_TYPE.WIDGET_UPDATE ,
text : "Widget property was updated" ,
source : {
type : ENTITY_TYPE . WIDGET ,
id : this.props.widgetId ,
name : this.props.widgetName ,
} ,
meta : metaUpdates ,
} ) ;
// extract payload from updates
const payload = [ . . . this . batchMetaUpdates ] ;
//clear batch updates
this . batchMetaUpdates = [ ] ;
this . handleBatchUpdateWidgetMetaProperties ( payload ) ;
} ;
getMetaPropPath = ( propertyName : string | undefined ) = > {
// look at this.props.__metaOptions, check for metaPropPath value
// if they exist, then update the propertyName
// Below code of updating metaOptions can be removed once we have ListWidget v2 where we better manage meta values of ListWidget.
const metaOptions = this . props . __metaOptions ;
if ( ! metaOptions ) return ;
return ` ${ metaOptions . metaPropPrefix } . ${ this . props . widgetName } . ${ propertyName } [ ${ metaOptions . index } ] ` ;
} ;
handleBatchUpdateWidgetMetaProperties = (
batchMetaUpdates : batchUpdateWidgetMetaPropertyType ,
) = > {
//if no updates ignore update call
if ( ! batchMetaUpdates || isEmpty ( batchMetaUpdates ) ) return ;
const { syncBatchUpdateWidgetMetaProperties } = this . context ;
const widgetId = this . props . metaWidgetId || this . props . widgetId ;
if ( syncBatchUpdateWidgetMetaProperties ) {
const metaOptions = this . props . __metaOptions ;
const consolidatedUpdates = batchMetaUpdates . reduce (
2024-07-31 15:41:28 +00:00
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2023-03-30 04:54:29 +00:00
( acc : any , { propertyName , propertyValue } ) = > {
acc . push ( { widgetId , propertyName , propertyValue } ) ;
if ( metaOptions ) {
acc . push ( {
widgetId : metaOptions.widgetId ,
propertyName : this.getMetaPropPath ( propertyName ) ,
propertyValue ,
} ) ;
}
return acc ;
} ,
[ ] ,
) ;
syncBatchUpdateWidgetMetaProperties ( consolidatedUpdates ) ;
}
batchMetaUpdates . forEach ( ( { actionExecution , propertyName } ) = >
this . addPropertyForEval ( propertyName , actionExecution ) ,
) ;
this . setState ( { } , ( ) = > {
// react batches the setState call
// this will result in batching multiple updateWidgetMetaProperty calls.
this . debouncedTriggerEvalOnMetaUpdate ( ) ;
} ) ;
} ;
2020-10-06 09:01:51 +00:00
2022-05-25 09:46:14 +00:00
handleUpdateWidgetMetaProperty = (
2021-03-01 14:56:47 +00:00
propertyName : string ,
2022-02-25 20:46:04 +00:00
propertyValue : unknown ,
2022-05-25 09:46:14 +00:00
actionExecution? : DebouncedExecuteActionPayload ,
) = > {
const { syncUpdateWidgetMetaProperty } = this . context ;
2023-02-14 16:07:31 +00:00
/ * *
* Some meta widget will have the actual widget 's widgetId as it' s widgetId .
* Eg - these are the widgets that are present in the first row of the List widget .
* For these widgets , it ' s expected for the meta updates to not go into the actual widgetId
* but a different internal id as over page changes the first row widgets should reflect distinct
* values entered in that particular page .
*
* Note : metaWidgetId would be undefined for all the non meta - widgets .
* /
const widgetId = this . props . metaWidgetId || this . props . widgetId ;
2021-03-01 14:56:47 +00:00
2022-05-25 09:46:14 +00:00
if ( syncUpdateWidgetMetaProperty ) {
syncUpdateWidgetMetaProperty ( widgetId , propertyName , propertyValue ) ;
// look at this.props.__metaOptions, check for metaPropPath value
// if they exist, then update the propertyName
// Below code of updating metaOptions can be removed once we have ListWidget v2 where we better manage meta values of ListWidget.
const metaOptions = this . props . __metaOptions ;
2023-03-30 04:54:29 +00:00
const metaPropPath = this . getMetaPropPath ( propertyName ) ;
if ( metaOptions && metaPropPath ) {
2022-05-25 09:46:14 +00:00
syncUpdateWidgetMetaProperty (
metaOptions . widgetId ,
2023-03-30 04:54:29 +00:00
metaPropPath ,
2022-05-25 09:46:14 +00:00
propertyValue ,
) ;
2020-10-06 16:47:16 +00:00
}
2022-05-25 09:46:14 +00:00
}
this . addPropertyForEval ( propertyName , actionExecution ) ;
this . setState ( { } , ( ) = > {
// react batches the setState call
// this will result in batching multiple updateWidgetMetaProperty calls.
this . debouncedTriggerEvalOnMetaUpdate ( ) ;
2020-10-06 09:01:51 +00:00
} ) ;
2022-05-25 09:46:14 +00:00
} ;
2020-10-06 09:01:51 +00:00
updatedProps = ( ) = > {
return {
2022-05-25 09:46:14 +00:00
. . . this . initialMetaState , // this contains stale default values and are used when widget is reset. Ideally, widget should reset to its default values instead of stale default values.
. . . this . props , // if default values are changed we expect to get new values from here.
. . . this . props . metaState ,
2020-10-06 09:01:51 +00:00
} ;
} ;
render() {
2022-05-25 09:46:14 +00:00
return (
< WrappedWidget
{ . . . this . updatedProps ( ) }
2023-03-30 04:54:29 +00:00
commitBatchMetaUpdates = { this . commitBatchMetaUpdates }
pushBatchMetaUpdates = { this . pushBatchMetaUpdates }
2022-05-25 09:46:14 +00:00
updateWidgetMetaProperty = { this . updateWidgetMetaProperty }
/ >
) ;
2020-10-06 09:01:51 +00:00
}
2022-05-25 09:46:14 +00:00
}
const mapStateToProps = ( state : AppState , ownProps : WidgetProps ) = > {
return {
2023-02-14 16:07:31 +00:00
metaState : getWidgetMetaProps ( state , ownProps ) ,
2022-05-25 09:46:14 +00:00
} ;
2020-10-06 09:01:51 +00:00
} ;
2022-05-25 09:46:14 +00:00
return connect ( mapStateToProps ) ( MetaHOC ) ;
}
2020-10-06 09:01:51 +00:00
export default withMeta ;