PromucFlow_constructor/contributions/ServerCodeContributionsGuidelines/PluginCodeContributionsGuidelines.md

335 lines
17 KiB
Markdown
Raw Permalink Normal View History

## Plugin Code Contribution Guidelines
Please follow the given guidelines to make sure that your commit sails through the review process without any
hiccups.
### Steps to create a new plugin
At this point, we assume that you have Appsmith's server code base setup locally. If not, please check out [the guide here](../ServerSetup.md).
1. Create a new maven module in the folder: `app/server/appsmith-plugins` via the command:
```
mvn archetype:generate \
-DgroupId=com.external.plugins \
-DartifactId=helloWorldPlugin \
-DarchetypeArtifactId=maven-archetype-quickstart \
-DinteractiveMode=false
```
Replace the `artifactId` with your plugin name. This tutorial will use `helloWorldPlugin` as a place-holder.
This command will generate a folder called `helloWorldPlugin` with default source code in the `appsmith-plugins` directory.
2. Navigate to your plugin code with your favourite IDE.
3. Copy the required properties in the plugin's `pom.xml` file. Example:
```
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>11</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<plugin.id>hello-world-plugin</plugin.id>
<plugin.class>com.external.plugins.HelloWorldPlugin</plugin.class>
<plugin.version>1.0-SNAPSHOT</plugin.version>
<plugin.provider>tech@appsmith.com</plugin.provider>
<plugin.dependencies/>
</properties>
```
Replace the properties `plugin.id` and `plugin.class` with your plugin name.
4. Replace the default dependencies generated by maven with the following:
```
<dependencies>
<dependency>
<groupId>org.pf4j</groupId>
<artifactId>pf4j-spring</artifactId>
<version>0.7.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.appsmith</groupId>
<artifactId>interfaces</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
<scope>provided</scope>
</dependency>
<!-- Test Dependencies -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<version>3.2.11.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.1.0</version>
<scope>test</scope>
</dependency>
</dependencies>
```
5. Add the `build` command to `pom.xml`:
```
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<configuration>
<minimizeJar>false</minimizeJar>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Plugin-Id>${plugin.id}</Plugin-Id>
<Plugin-Class>${plugin.class}</Plugin-Class>
<Plugin-Version>${plugin.version}</Plugin-Version>
<Plugin-Provider>${plugin.provider}</Plugin-Provider>
</manifestEntries>
</transformer>
</transformers>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<includeScope>runtime</includeScope>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
```
6. Add a file called `plugin.properties` with the following content:
```
plugin.id=hello-world-plugin
plugin.class=com.external.plugins.HelloWorldPlugin
plugin.version=1.0-SNAPSHOT
plugin.provider=tech@appsmith.com
plugin.dependencies=
```
Please remember that the `plugin.class` and `plugin.id` MUST be the same as the ones defined in your `pom.xml`.
7. Navigate to your plugin's Java source folder `src/` and create a new class file `HelloWorldPlugin.java`. This is the same name as defined in your `pom.xml` property `plugin.class`.
8. Ensure that the class has the following structure:
```
public class HelloWorldPlugin extends BasePlugin {
public HelloWorldPlugin(PluginWrapper wrapper) {
super(wrapper);
}
@Slf4j
@Extension
public static class HelloWorldPluginExecutor implements PluginExecutor<Object> {
}
}
```
The `BasePlugin` & `PluginExecutor` classes define the basic operations of plugin execution
9. Add the plugin to the DB so that the Appsmith platform understands that a new plugin has been created. This can be done via the `DatabaseChangelog.java` file. Eg:
```
@ChangeSet(order = "076", id = "add-hello-world-plugin", author = "")
public void addHelloWorldPlugin(MongoTemplate mongoTemplate) {
Plugin plugin = new Plugin();
plugin.setName("Hello World Plugin");
plugin.setType(PluginType.DB);
plugin.setPackageName("hello-world-plugin");
plugin.setUiComponent("DbEditorForm");
plugin.setResponseType(Plugin.ResponseType.JSON);
plugin.setIconLocation("https://your-plugin-icon-location.png");
plugin.setDocumentationLink("https://link-to-plugin-documentation.html");
plugin.setDefaultInstall(true);
feat: Add functionality to opt out plugin from airgap instance (#22098) ## Description As a part of supporting airgap instances we want to restrict the plugins which have any public dependency. This PR introduces a config setting for plugins to opt out of airgap. Also as a part of this exercise we are also adding a setting for CS dependency which can be utilised in future if our customers wants to opt out of CS dependent plugins. Corresponding EE PR: https://github.com/appsmithorg/appsmith-ee/pull/1258 > TL;DR: Provide a way for plugins supported in Appsmith to opt-out of airgap instances Fixes https://github.com/appsmithorg/appsmith/issues/21499 ## Type of change - New feature (non-breaking change which adds functionality) ## How Has This Been Tested? - Manual ## Checklist: ### Dev activity - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [x] 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 ### 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
2023-04-06 17:45:03 +00:00
// Field to distinguish if the plugin is supported in air-gap instance, by default all the plugins will be
// supported. One can opt out by adding this field in DB object. Generally SaaS plugins and DB which can't be
// self-hosted can be a candidate for opting out of air-gap
plugin.setSupportedForAirGap(false);
// Config to set if the plugin has any dependency on cloud-services
plugin.setIsDependentOnCS(true);
try {
mongoTemplate.insert(plugin);
} catch (DuplicateKeyException e) {
log.warn(plugin.getPackageName() + " already present in database.");
}
installPluginToAllWorkspaces(mongoTemplate, plugin.getId());
}
```
10. Add the files `editor.json` and `form.json` in the `src/main/resources` folder. These JSON files are required to render the UI for your plugin. Samples are given below:
```
# form.json
{
"form": [
{
"sectionName": "Details",
"id": 1,
"children": [
{
"label": "DB Username",
"configProperty": "datasourceConfiguration.authentication.username",
"controlType": "INPUT_TEXT",
"isRequired": true,
"placeholderText": "",
"initialValue": ""
},
{
"label": "DB Password",
"configProperty": "datasourceConfiguration.authentication.password",
"controlType": "INPUT_TEXT",
"dataType": "PASSWORD",
"initialValue": "",
"encrypted": true
}
]
}
]
}
# editor.json
{
"editor": [
{
"sectionName": "",
"id": 1,
"children": [
{
"label": "",
"configProperty": "actionConfiguration.body",
"controlType": "QUERY_DYNAMIC_TEXT"
}
]
}
]
}
```
11. Compile & run your code via the command:
```
cd app/server && ./build.sh && cd scripts && ./start-dev-server.sh
```
### Code Design
As much as possible, please try to abide by the following code design:
1. All plugins are part of the package: `com.external.plugins`.
2. All plugin src code resides in the path: [app/server/appsmith-plugins](https://github.com/appsmithorg/appsmith/tree/release/app/server/appsmith-plugins).
3. To integrate the new plugin:
- add corresponding changes to the file `DatabaseChangelog.java` like
[here in DatabaseChangelog.java](https://github.com/appsmithorg/appsmith/blob/release/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog.java#L1258).
- add your plugin to [this pom.xml file](https://github.com/appsmithorg/appsmith/blob/release/app/server/appsmith-plugins/pom.xml).
4. Each plugin must implement all the methods defined by the interface in [PluginExecutor.java](https://github.com/appsmithorg/appsmith/blob/release/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/plugins/PluginExecutor.java), for example: [MySqlPlugin.java](https://github.com/appsmithorg/appsmith/blob/release/app/server/appsmith-plugins/mysqlPlugin/src/main/java/com/external/plugins/MySqlPlugin.java) and [RestApiPlugin.java](https://github.com/appsmithorg/appsmith/blob/release/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/plugins/RestApiPlugin.java).
5. The form to be filled by the user when creating a new datasource is rendered by the configuration file [form.json](https://github.com/appsmithorg/appsmith/blob/release/app/server/appsmith-plugins/firestorePlugin/src/main/resources/form.json). For details, please see [this mapping](https://github.com/appsmithorg/appsmith/tree/release/static/form.png) between `form.json` and the rendered web page.
6. The form where user enters the query for execution is rendered by the configuration file [editor.json](https://github.com/appsmithorg/appsmith/blob/release/app/server/appsmith-plugins/firestorePlugin/src/main/resources/editor.json).
For details, please see [this mapping](https://github.com/appsmithorg/appsmith/tree/release/static/editor.png)
between `editor.json` and the rendered web page.
### Package Dependency
1. We use `Maven` to manage package dependencies, hence please add all your dependencies in `POM` file as shown in this
[pom.xml file](https://github.com/appsmithorg/appsmith/blob/release/app/server/appsmith-plugins/postgresPlugin/pom.xml) for postgreSQL plugin.
2. Always use release version of the packages or release candidate if the release version is not available.
3. Build and test your code to check for any dependency conflicts and resolve them.
### Source Code
1. Please name your file like `DbnamePlugin.java`, for example: [PostgresPlugin.java](https://github.com/appsmithorg/appsmith/blob/release/app/server/appsmith-plugins/postgresPlugin/src/main/java/com/external/plugins/PostgresPlugin.java).
2. When importing packages make sure that only those packages are imported that are used, and refrain from using `xyz.*`.
3. Refrain from using magic strings or magic numbers. Whenever possible, assign them to a `private static` identifier
for usage. For example, instead of using `"date"` string directly, assign it to a `private static` identifier like `private static final String DATE_COLUMN_TYPE_NAME = "date";`
4. Appsmith's API server is powered by [Spring webflux](https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux) and hence expects programmers to
follow a [reactive model](https://www.reactive-streams.org/).
- In case a reactive driver is available for the plugin that you want to add, please use it after verifying
that it supports all of the commonly used data types. In case the reactive driver does not support enough data types,
please use any other well known and trusted driver.
- In case the driver that you wish to use does not follow reactive model, please enforce reactive model as shown
in the plugin code [PostgresPlugin.java](https://github.com/appsmithorg/appsmith/blob/release/app/server/appsmith-plugins/postgresPlugin/src/main/java/com/external/plugins/PostgresPlugin.java).
- Make sure that your [Mono/Flux](https://projectreactor.io/docs/core/release/reference/index.html#core-features)
object is processed on a new dedicated thread by chaining [.subscribeOn(...)](https://projectreactor.io/docs/core/release/reference/index.html#schedulers)
method call to the Mono/Flux object. For reference, please check its usage in [PostgresPlugin.java](https://github.com/appsmithorg/appsmith/blob/release/app/server/appsmith-plugins/postgresPlugin/src/main/java/com/external/plugins/PostgresPlugin.java).
5. Make sure to handle any exceptions
- Always check for a stale connection (i.e. if the connection to the datasource has been closed or invalidated)
before query execution and throw an uncaught `StaleConnectionException`.
This exception is caught by Appsmith's framework and a new connection is established before running the query.
For reference, please check the usage of StaleConnectionException in
[PostgresPlugin.java](https://github.com/appsmithorg/appsmith/blob/release/app/server/appsmith-plugins/postgresPlugin/src/main/java/com/external/plugins/PostgresPlugin.java).
- Print the exceptions on console along with thread information like `System.out.println(Thread.currentThread().
getName() + ": <your
error msg> : " + exception.msg);`
6. Always check for `null` values before using objects.
7. Comment your code in hard to understand areas.
8. In case your method implementation is too large (use your own judgement here), please refactor it into smaller
methods.
### Unit Test
1. Every plugin must have its own unit test file like [PostgresPluginTest.java](https://github.com/appsmithorg/appsmith/blob/release/app/server/appsmith-plugins/postgresPlugin/src/test/java/com/external/plugins/PostgresPluginTest.java) for [PostgresPlugin.java](https://github.com/appsmithorg/appsmith/blob/release/app/server/appsmith-plugins/postgresPlugin/src/main/java/com/external/plugins/PostgresPlugin.java).
2. The test file must be named as `PluginNameTest.java` e.g. `PostgresPluginTest.java`
3. Use Mockito framework to test using mock objects whenever testing against a real instance is not possible, for
example, when using [testcontainers](https://www.testcontainers.org/) is not possible.
4. Please test the following cases in your unit test file:
- Successfully establishing connection to the datasource.
- Reject invalid credentials.
- Successful query execution.
- Stale connection exception (please see the [Source Code](#source-code) section above for details).
- In case of a database plugin, test that it is able to fetch tables/collection information from database and key
constraints information as well. For example, check out the `testStructure()` method in [PostgresPluginTest.
java](https://github.com/appsmithorg/appsmith/blob/release/app/server/appsmith-plugins/postgresPlugin/src/test/java/com/external/plugins/PostgresPluginTest.java).
5. Reference test files:
- [PostgresPluginTest.java](https://github.com/appsmithorg/appsmith/blob/release/app/server/appsmith-plugins/postgresPlugin/src/test/java/com/external/plugins/PostgresPluginTest.java).
- [MySqlPluginTest.java](https://github.com/appsmithorg/appsmith/blob/release/app/server/appsmith-plugins/mysqlPlugin/src/test/java/com/external/plugins/MySqlPluginTest.java).
### Code Review
1. Before you start working on a code change, please check with any of the maintainers regarding the same. You
may initiate a discussion on Github Discussions page or comment on any of the open issues or directly reach out on
our Discord channel. It will increase the chances of your pull request getting merged.
2. Before you raise a pull request, please check if there is a bug that has been raised to address it. If not, then
raise a bug yourself and link it with your pull request.
3. You may share you pull request on Appsmith's discord channel or send an email to support@appsmith.com for attention.
4. Please be as descriptive as possible in the pull request's description section. Clearly mention how the code has
been tested. If possible, add snapshots too.
### Need Assistance
- If you are unable to resolve any issue, please initiate a Github discussion or send an email to support@appsmith.com. We'll be happy to help you.
- In case you notice any discrepancy, please raise an issue on Github and/or send an email to support@appsmith.com.