## 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
335 lines
17 KiB
Markdown
335 lines
17 KiB
Markdown
## 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);
|
|
|
|
// 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.
|