How to build an extension in Auravant from scratch
I imagine you became an Auravant developer and immediatly started some brilliant project. Then you spent a little while going around to know how to face the extension.
"Where do I have to get the information I want?" and "What do I have to use to interact with the platform?" might be both examples of what you asked yourself.
In this guide I tell you what your starting point can be.
How to read Auravant's information
Auravant offers extension developers to interact with the platform. We can create fields, map labours and activities, draw polygons on the map, and a bit long list.
But, how can you get to use that information?
Before we get into the how you use it, let's understand how information is organized.
If you already know Auravant, and you already know how the SDK, the API and its data model works, go straight to how to build an extension.
Data model
The information that a user uploads and manages is distributed as follows:
Basically, in Auravant we have fields, these are grouped into farms.
On the other hand, we have campaigns, which bring together tasks for each set Field/Yeargroup. The yeargroup is simply the year mentioned.
Finally, we have the labours, which have a type
, a status
, associated inputs
and extra data specific to each task.
Now let's get to the how! We're almost there...
SDK
The main tool that Auravant offers for interaction with the platform from an extension is the SDK. For those who do not know what an SDK (Software Development Kit) is, here I drop off some information.
The Auravant's SDK it's divided into modules.
Each module groups tools that work in the same application area. For example, the map module is used to interact with the main map.
Currently, these are the modules that are available in our SDK:
Además, el SDK tiene algunas variables globales que te servirán para ciertas aplicaciones. Estas son, platform y token.
The first one tells you on which platform the extension is running. It can be web, android or ios. The second variable is the unique token belonging to the extension that is required to access the Auravant's API.
It is necessary to take them into account for two reasons. The first reason is that they should not be used in the extension if the SDK is loaded because they could be overwritten. And on the other hand, the token must not be hardcoded.
API
There are platform-specific features that cannot be accessed from the SDK. For this, Auravant has an API available with a series of endpoints that allow obtaining certain information and performing operations on the platform.
However, there are two things to keep in mind when using these endpoints from an extension. Remember to use the token provided by the SDK to perform API queries and enable the features in the claimset.
The claimset is a list of permissions, corresponding to each functionality, that you must enable from the developer space. Each extension has its own claimset. The user who will use the extension will be notified of the functionalities that the extension requests to use, and he must authorize these operations when installing the extension.
The API reference can be accessed from here
Set-up React and Vite project
Let's move on to building an extension from scratch.
We'll be using React + Vite in this article, however, an extension can be built on whatever technology you want.
The first thing we will do is create our project in Vite:
foo > npm create vite@latest ext-example -- --template react
foo > cd ext-example
foo > npm install
Once the project is created in Vite, we are going to open it with our preferred code editor, and go to the vite.config.js file. Here we are going to add the base: ""
configuration that will be used to later upload our extension to Auravant and make it run correctly.
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
base: ""
});
To finish with the configurations, we now go to index.html and load the SDK by copying the following script at the end of the body
:
<script src="https://auraview.auravant.com/sdk/v1.0/aura-package_v1.0.js"></script>
Alright, let's do something simple and upload the first commit to the platform.
I am going to delete everything that Vite generates for me as a boilerplate, and I will be left with the following files.
En main.jsx pegamos este código:
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
if(avt.platform === "web"){
window.addEventListener("readySDK", loadExt);
} else {
loadExt();
};
function loadExt() {
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("root")
);
};
Because the SDK starts loading when the extension is opened, we need to wait for it to finish loading before we can start loading our code. For this, we listen to an event provided by the SDK itself called readySDK.
In the case of Android or IOS, it is not necessary to wait for this event, since in these environments the SDK automatically loads before the extension.
In App.jsx we are going to paste this code, which simply displays a title with the text "Hello world!"
const App = () => {
return (
<h1
style={{
color: "white",
textAlign: "center"
}}
>Hello World!</h1>
)
};
export default App;
Now we can compile and upload our extension.
foo > npm run build
We go to the dist folder and zip its contents.
Set-up the extension in Auravant
Now we need to create the extension in Auravant. For this we go to the developer space in the menu:
In the menu you will find a list of extensions (if you have some) and a green button below everything that will say "Create new". When we click it we get this
Once this is done we can see the extension from the extensions menu. A yellow sign that says "DEV" appears on the extension's icon.
When opening the extension in dev we will also see a Developer mode banner:
Here we need to drag the zip that we got when we set our extension in React. Now we are going to see our first extension running correctly in Auravant.
Adding the first SDK function
Let's give the extension a little more life by connecting it to Auravant. We can replicate the farm and field selection functionality using the SDK. Then, with the information obtained, we will show how many hectares the selected field has.
Note - For selectors I'm using React Select library.
Copy the following code into App.jsx
import { useEffect, useState } from 'react';
import Select from 'react-select';
const App = () => {
// Set the states to render the extension
const [farms, setFarms] = useState(avt.generalData.getFarms()) // Farm information
const [farmOptions, setFarmOptions] = useState([]); // Farm select options
const [fieldOptions, setFieldOptions] = useState([]); // Field select options
const [farmSelected, setFarmSelected] = useState({}); // Selected farm info
const [fieldSelected, setFieldSelected] = useState({}); // Selected field info
const [area, setArea] = useState(); // Selected field area
useEffect(() => {
// At the very first the options are loaded with farm information
setFarmOptions(farms.map(farm => {
return {
value: farm,
label: farm.nombre
}
}));
},[]);
// Once a farm is selected we load field options
useEffect(()=> {
if(Object.keys(farmSelected).length){
let fields = farmSelected.lotes;
setFieldOptions(fields.map(field => {
return {
value: field,
label: field.nombre
}
}));
}
},[farmSelected]);
// When farm changes the new one is setted and the field data is cleaned
const handleFarmChange = (option) => {
setFarmSelected(option.value);
setFieldOptions([]);
setFieldSelected({});
};
// When field value changes we set the value of the field and also the area of the selected field
const handleFieldChange = (option) => {
setFieldSelected(option.value);
setArea(option.value.area);
};
return (
<div>
<h1
style={{
color: "white",
textAlign: "center"
}}
>Selecci ón de campo y lote</h1>
<div style={{ marginBottom: "8px"}}>
<span style={{ color: "white" }}>Select farm:</span>
<Select onChange={(option) => handleFarmChange(option)} options={farmOptions} />
</div>
{/* Once a farm is selected we render the field selector */}
{
Object.keys(farmSelected).length ?
<div style={{ marginBottom: "8px"}}>
<span style={{ color: "white" }}>Select field:</span>
<Select onChange={(option) => handleFieldChange(option)} options={fieldOptions} />
</div> : ""
}
{/* Once a field is selected we render the area text */}
{
Object.keys(fieldSelected).length && area ?
<div style={{ marginBottom: "8px"}}>
<span style={{ color: "white" }}>
The selected field has {area} ha
</span>
</div> : ""
}
</div>
)
};
export default App;
Here we use avt.generalData.getFarms() to get all the information of the user's farms and their corresponding fields.
API request
Now that we have the information for a particular field available, we can use this information to perform more detailed queries via the API.
For instance, we could obtain the average NDVI of the selected field, in the last month. Let's do it.
For that, I'll be using API Fields.
Starting from the previous example, we modify App.jsx:
import { useEffect, useState } from 'react';
import Select from 'react-select';
const App = () => {
const [farms, setFarms] = useState(avt.generalData.getFarms())
const [farmOptions, setFarmOptions] = useState([]);
const [fieldOptions, setFieldOptions] = useState([]);
const [farmSelected, setFarmSelected] = useState({});
// Add this state to save NDVI data
const [NDVIMean, setNDVIMean] = useState([]);
useEffect(() => {
setFarmOptions(farms.map(farm => {
return {
value: farm,
label: farm.nombre
}
}));
},[]);
useEffect(()=> {
if(Object.keys(farmSelected).length){
let fields = farmSelected.lotes;
setFieldOptions(fields.map(field => {
return {
value: field,
label: field.nombre
}
}));
}
},[farmSelected]);
// Here we clean the NDVI data when a farm is change
const handleFarmChange = (option) => {
setFarmSelected(option.value);
setFieldOptions([]);
setNDVIMean([]);
};
// Now it searches the NDVI for the selected field from a month ago
const handleFieldChange = (option) => {
let field = option.value;
let today = new Date().toISOString().slice(0, 10);
let sevenDaysAgo = new Date(new Date().setDate(new Date().getDate() - 30))
sevenDaysAgo = sevenDaysAgo.toISOString().slice(0, 10);
// Busco información de NDVI de la API:
fetch(
`https://api.auravant.com/api/fields/ndvi?field_id=${field.id}&date_from=${sevenDaysAgo}&date_to=${today}`,
{
headers: {
"Authorization": `Bearer ${token}`
}
}
)
.then((response) => {
if (response.ok) {
return response.json();
} else {
throw new Error();
}
})
.then((response) => {
setNDVIMean(response.ndvi);
})
.catch(() => {
// If something's wrong then we dispatch a toast to give feedback to the user.
avt.interface.async_toast({
type: "error",
message: "No NDVI data could been obtained",
options: {
timeOut: 5000,
closeButton: true,
},
});
});
};
return (
<div>
<h1
style={{
color: "white",
textAlign: "center"
}}
>Farm and Field selection</h1>
{/* Si existen opciones para campos muestro el selector de campos */}
{
farmOptions.length ?
<div style={{ marginBottom: "8px"}}>
<span style={{ color: "white" }}>Select a farm:</span>
<Select onChange={(option) => handleFarmChange(option)} options={farmOptions} />
</div> : ""
}
{/* Una vez seleecionado el campo y se cargan sus opciones, renderizamos el selector de lotes */}
{
Object.keys(farmSelected).length && fieldOptions.length ?
<div style={{ marginBottom: "8px"}}>
<span style={{ color: "white" }}>Select a field:</span>
<Select onChange={(option) => handleFieldChange(option)} options={fieldOptions} />
</div> : ""
}
{/* Una vez seleecionado el lote y se buscan los datos de NDVI se renderizan todos los datos obtenidos */}
{
NDVIMean.length ?
NDVIMean.map(ndvi => {
return (
<div>
<span style={{ color: "white" }}>Date: {ndvi.date} / Mean NDVI: {ndvi.ndvi_mean}</span>
</div>
)
})
: ""
}
</div>
)
};
export default App;
And that's all for our extension that informs the user of the average NDVI over a month.
Now I encourage you to explore your own ideas, what other APIs you could use, or what functions of the SDK would make your work easier and how you could take advantage of Auravant to be the Operating System of your Agro applications.