Create an API-based Chart

Creating an API-based Chart

Sometimes, charts data are complicated and closely tied to your business. Forest Admin allows you to code how the chart is computed. Choose API as the data source when configuring your chart.
Forest Admin will make the HTTP call to Smart Chart URL when retrieving the chart values for the rendering.

Value API-based Chart

On our Live Demo, we have a MRR value chart which computes our Monthly Recurring Revenue. This chart queries the Stripe API to get all charges made in the current month (in March for this example).
SQL
Mongodb
Rails
Django
When serializing the data, we use the Liana.StatSerializer() serializer. Check the value syntax below.
1
{ value: <number> }
Copied!
/routes/dashboard.js
1
const P = require('bluebird');
2
const express = require('express');
3
const router = express.Router();
4
const Liana = require('forest-express-sequelize');
5
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
6
const moment = require('moment');
7
8
...
9
10
router.post('/stats/mrr', (req, res) => {
11
let mrr = 0;
12
13
let from = moment.utc('2018-03-01').unix();
14
let to = moment.utc('2018-03-31').unix();
15
16
return stripe.charges
17
.list({
18
created: { gte: from, lte: to }
19
})
20
.then((response) => {
21
return P.each(response.data, (charge) => {
22
mrr += charge.amount;
23
});
24
})
25
.then(() => {
26
let json = new Liana.StatSerializer({
27
value: mrr
28
}).perform();
29
30
res.send(json);
31
});
32
});
33
34
...
35
36
module.exports = router;
Copied!
When serializing the data, we use the Liana.StatSerializer() serializer. Check the value syntax below.
1
{ value: <number> }
Copied!
/routes/dashboard.js
1
const P = require('bluebird');
2
const express = require('express');
3
const router = express.Router();
4
const Liana = require('forest-express-mongoose');
5
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
6
const moment = require('moment');
7
8
...
9
10
router.post('/stats/mrr', (req, res) => {
11
let mrr = 0;
12
13
let from = moment.utc('2018-03-01').unix();
14
let to = moment.utc('2018-03-31').unix();
15
16
return stripe.charges
17
.list({
18
created: { gte: from, lte: to }
19
})
20
.then((response) => {
21
return P.each(response.data, (charge) => {
22
mrr += charge.amount;
23
});
24
})
25
.then(() => {
26
let json = new Liana.StatSerializer({
27
value: mrr
28
}).perform();
29
30
res.send(json);
31
});
32
});
33
34
...
35
36
module.exports = router;
Copied!
When serializing the data, we use the serialize_model() method. Check the value syntax below.
1
{ value: <number> }
Copied!
/config/routes.rb
1
Rails.application.routes.draw do
2
# MUST be declared before the mount ForestLiana::Engine.
3
namespace :forest do
4
post '/stats/mrr' => 'charts#mrr'
5
end
6
7
mount ForestLiana::Engine => '/forest'
8
end
Copied!
/app/controllers/forest/charts_controller.rb
1
class Forest::ChartsController < ForestLiana::ApplicationController
2
def mrr
3
mrr = 0
4
5
from = Date.parse('2018-03-01').to_time(:utc).to_i
6
to = Date.parse('2018-03-31').to_time(:utc).to_i
7
8
Stripe::Charge.list({
9
created: { gte: from, lte: to },
10
limit: 100
11
}).each do |charge|
12
mrr += charge.amount / 100
13
end
14
15
stat = ForestLiana::Model::Stat.new({ value: mrr })
16
render json: serialize_model(stat)
17
end
18
end
Copied!
app/urls.py
1
from django.urls import path
2
from django.views.decorators.csrf import csrf_exempt
3
4
from . import views
5
6
app_name = 'app'
7
urlpatterns = [
8
path('/stats/mrr', csrf_exempt(views.ChartsMrrView.as_view()), name='stats-mrr')
9
]
Copied!
app/views.py
1
import uuid
2
import stripe
3
from datetime import datetime
4
import pytz
5
6
from django.views import generic
7
from django.http import JsonResponse
8
9
10
class ChartsMrrView(generic.ListView):
11
12
def post(self, request, *args, **kwargs):
13
mrr = 0
14
from_ = int(datetime(2018, 3, 1, 0, 0, 0, 0, tzinfo=pytz.UTC).timestamp())
15
to_ = int(datetime(2018, 3, 31, 0, 0, 0, 0, tzinfo=pytz.UTC).timestamp())
16
17
stripe.api_key = os.getenv('STRIPE_SECRET_KEY')
18
19
response = stripe.Charge.list(
20
limit=100,
21
created={'gte': from_, 'lte': to_}
22
)
23
24
for charge in response['data']:
25
mrr += charge['amount']
26
27
res = {
28
'data': {
29
'attributes': {
30
'value': {
31
'countCurrent': mrr
32
}
33
},
34
'type': 'stats',
35
'id': uuid.uuid4()
36
}}
37
38
return JsonResponse(res, safe=False)
Copied!

Repartition API-based Chart

On our Live Demo, we have a Charges repartition chart which shows a repartition chart distributed by credit card country. This chart queries the Stripe API to get all charges made in the current month (in March for this example) and check the credit card country.
SQL
Mongodb
Rails
Django
When serializing the data, we use the Liana.StatSerializer() serializer. Check the value syntax below.
1
{
2
value: [{
3
key: <string> ,
4
value: <number>
5
}, {
6
key: <string> ,
7
value: <number>
8
}, …]
9
}
Copied!
/routes/dashboard.js
1
const _ = require('lodash');
2
const P = require('bluebird');
3
const express = require('express');
4
const router = express.Router();
5
const Liana = require('forest-express-sequelize');
6
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
7
const moment = require('moment');
8
9
router.post('/stats/credit-card-country-repartition', Liana.ensureAuthenticated, (req, res) => {
10
let repartition = [];
11
12
let from = moment.utc('2018-03-01').unix();
13
let to = moment.utc('2018-03-20').unix();
14
15
return stripe.charges
16
.list({
17
created: { gte: from, lte: to }
18
})
19
.then((response) => {
20
return P.each(response.data, (charge) => {
21
let country = charge.source.country || 'Others';
22
23
let entry = _.find(repartition, { key: country });
24
if (!entry) {
25
repartition.push({ key: country, value: 1 });
26
} else {
27
entry.value++;
28
}
29
});
30
})
31
.then(() => {
32
let json = new Liana.StatSerializer({
33
value: repartition
34
}).perform();
35
36
res.send(json);
37
});
38
});
39
40
module.exports = router;
Copied!
When serializing the data, we use the Liana.StatSerializer() serializer. Check the value syntax below.
1
{
2
value: [{
3
key: <string> ,
4
value: <number>
5
}, {
6
key: <string> ,
7
value: <number>
8
}, …]
9
}
Copied!
/routes/dashboard.js
1
const _ = require('lodash');
2
const P = require('bluebird');
3
const express = require('express');
4
const router = express.Router();
5
const Liana = require('forest-express-mongoose');
6
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
7
const moment = require('moment');
8
9
router.post('/stats/credit-card-country-repartition', Liana.ensureAuthenticated, (req, res) => {
10
let repartition = [];
11
12
let from = moment.utc('2018-03-01').unix();
13
let to = moment.utc('2018-03-20').unix();
14
15
return stripe.charges
16
.list({
17
created: { gte: from, lte: to }
18
})
19
.then((response) => {
20
return P.each(response.data, (charge) => {
21
console.log(charge.source);
22
let country = charge.source.country || 'Others';
23
24
let entry = _.find(repartition, { key: country });
25
if (!entry) {
26
repartition.push({ key: country, value: 1 });
27
} else {
28
entry.value++;
29
}
30
});
31
})
32
.then(() => {
33
let json = new Liana.StatSerializer({
34
value: repartition
35
}).perform();
36
37
res.send(json);
38
});
39
});
40
41
module.exports = router;
Copied!
When serializing the data, we use the serialize_model() method. Check the value syntax below.
1
{
2
value: [{
3
key: <string> ,
4
value: <number>
5
}, {
6
key: <string> ,
7
value: <number>
8
}, …]
9
}
Copied!
/config/routes.rb
1
Rails.application.routes.draw do
2
# MUST be declared before the mount ForestLiana::Engine.
3
namespace :forest do
4
post '/stats/credit-card-country-repartition' => 'charts#credit_card_country_repartition'
5
end
6
7
mount ForestLiana::Engine => '/forest'
8
end
Copied!
1
class Forest::ChartsController < ForestLiana::ApplicationController
2
def credit_card_country_repartition
3
repartition = []
4
5
from = Date.parse('2018-03-01').to_time(:utc).to_i
6
to = Date.parse('2018-03-20').to_time(:utc).to_i
7
8
Stripe::Charge.list({
9
created: { gte: from, lte: to },
10
limit: 100
11
}).each do |charge|
12
country = charge.source.country || 'Others'
13
14
entry = repartition.find { |e| e[:key] == country }
15
if !entry
16
repartition << { key: country, value: 1 }
17
else
18
++entry[:value]
19
end
20
end
21
22
stat = ForestLiana::Model::Stat.new({ value: repartition })
23
render json: serialize_model(stat)
24
end
25
end
Copied!
app/urls.py
1
from django.urls import path
2
from django.views.decorators.csrf import csrf_exempt
3
4
from . import views
5
6
app_name = 'app'
7
urlpatterns = [
8
path('/stats/credit-card-country-repartition', csrf_exempt(views.CreditCardCountryRepartitionView.as_view()), name='stats-credit-card-country-repartition')
9
]
Copied!
app/views.py
1
import uuid
2
import stripe
3
from datetime import datetime
4
import pytz
5
6
from django.views import generic
7
from django.http import JsonResponse
8
9
10
class CreditCardCountryRepartitionView(generic.ListView):
11
12
def post(self, request, *args, **kwargs):
13
repartition = [];
14
from_ = int(datetime(2018, 3, 1, 0, 0, 0, 0, tzinfo=pytz.UTC).timestamp())
15
to_ = int(datetime(2018, 3, 31, 0, 0, 0, 0, tzinfo=pytz.UTC).timestamp())
16
17
stripe.api_key = os.getenv('STRIPE_SECRET_KEY')
18
19
response = stripe.Charge.list(
20
created={'gte': from_, 'lte': to_}
21
)
22
23
for charge in response['data']:
24
country = charge['source']['country'] or 'Others'
25
26
entry = next((x for x in repartition if x['key'] == country), None)
27
if entry is None:
28
repartition.append({'key': country, 'value': 1})
29
else:
30
entry['value'] += 1
31
32
res = {
33
'data': {
34
'attributes': {
35
'value': repartition,
36
},
37
'type': 'stats',
38
'id': uuid.uuid4()
39
}}
40
41
return JsonResponse(res, safe=False)
Copied!

Time-based API-based Chart

On our Live Demo, we have a Charges time-based chart which shows the number of charges per day. This chart queries the Stripe API to get all charges made in the current month (in March for this example) and group data by day.
SQL
Mongodb
Rails
Django
When serializing the data, we use the Liana.StatSerializer() serializer. Check the value syntax below.
1
{
2
value: [{
3
label: <string> ,
4
values: { value: <number> }
5
}, {
6
label: <string> ,
7
values: { value: <number> }
8
}, …]
9
}
Copied!
/routes/dashboard.js
1
const _ = require('lodash');
2
const P = require('bluebird');
3
const express = require('express');
4
const router = express.Router();
5
const Liana = require('forest-express-sequelize');
6
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
7
const moment = require('moment');
8
9
router.post('/stats/charges-per-day', (req, res) => {
10
let values = [];
11
12
let from = moment.utc('2018-03-01').unix();
13
let to = moment.utc('2018-03-31').unix();
14
15
return stripe.charges
16
.list({
17
created: { gte: from, lte: to }
18
})
19
.then((response) => {
20
return P.each(response.data, (charge) => {
21
let date = moment.unix(charge.created).startOf('day').format('LLL');
22
23
let entry = _.find(values, { label: date });
24
if (!entry) {
25
values.push({ label: date, values: { value: 1 } });
26
} else {
27
entry.values.value++;
28
}
29
});
30
})
31
.then(() => {
32
let json = new Liana.StatSerializer({
33
value: values
34
}).perform();
35
36
res.send(json);
37
});
38
});
39
40
module.exports = router;
Copied!
When serializing the data, we use the Liana.StatSerializer() serializer. Check the value syntax below.
1
{
2
value: [{
3
label: <string> ,
4
values: { value: <number> }
5
}, {
6
label: <string> ,
7
values: { value: <number> }
8
}, …]
9
}
Copied!
/routes/dashboard.js
1
const _ = require('lodash');
2
const P = require('bluebird');
3
const express = require('express');
4
const router = express.Router();
5
const Liana = require('forest-express-mongoose');
6
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
7
const moment = require('moment');
8
9
router.post('/stats/charges-per-day', (req, res) => {
10
let values = [];
11
12
let from = moment.utc('2018-03-01').unix();
13
let to = moment.utc('2018-03-31').unix();
14
15
return stripe.charges
16
.list({
17
created: { gte: from, lte: to }
18
})
19
.then((response) => {
20
return P.each(response.data, (charge) => {
21
let date = moment.unix(charge.created).startOf('day').format('LLL');
22
23
let entry = _.find(values, { label: date });
24
if (!entry) {
25
values.push({ label: date, values: { value: 1 } });
26
} else {
27
entry.values.value++;
28
}
29
});
30
})
31
.then(() => {
32
let json = new Liana.StatSerializer({
33
value: values
34
}).perform();
35
36
res.send(json);
37
});
38
});
39
40
module.exports = router;
Copied!
When serializing the data, we use the serialize_model() method. Check the value syntax below.
1
{
2
value: [{
3
label: <string> ,
4
values: { value: <number> }
5
}, {
6
label: <string> ,
7
values: { value: <number> }
8
}, …]
9
}
Copied!
/config/routes.rb
1
Rails.application.routes.draw do
2
# MUST be declared before the mount ForestLiana::Engine.
3
namespace :forest do
4
post '/stats/charges-per-day' => 'charts#charges_per_day'
5
end
6
7
mount ForestLiana::Engine => '/forest'
8
end
Copied!
/app/controllers/forest/charts_controller.rb
1
class Forest::ChartsController < ForestLiana::ApplicationController
2
def charges_per_day
3
values = []
4
5
from = Date.parse('2018-03-01').to_time(:utc).to_i
6
to = Date.parse('2018-03-31').to_time(:utc).to_i
7
8
Stripe::Charge.list({
9
created: { gte: from, lte: to },
10
limit: 100
11
}).each do |charge|
12
date = Time.at(charge.created).beginning_of_day.strftime("%d/%m/%Y")
13
entry = values.find { |e| e[:label] == date }
14
if !entry
15
values << { label: date, values: { value: 1 } }
16
else
17
++entry[:values][:value]
18
end
19
end
20
21
stat = ForestLiana::Model::Stat.new({ value: values })
22
render json: serialize_model(stat)
23
end
24
end
Copied!
app/urls.py
1
from django.urls import path
2
from django.views.decorators.csrf import csrf_exempt
3
4
from . import views
5
6
app_name = 'app'
7
urlpatterns = [
8
path('/stats/charges-per-day', csrf_exempt(views.ChargesPerDayView.as_view()), name='stats-charges-per-day')
9
]
Copied!
app/views.py
1
import uuid
2
import stripe
3
from datetime import datetime
4
import pytz
5
6
from django.views import generic
7
from django.http import JsonResponse
8
9
10
class ChargesPerDayView(generic.ListView):
11
12
def post(self, request, *args, **kwargs):
13
values = [];
14
from_ = int(datetime(2018, 3, 1, 0, 0, 0, 0, tzinfo=pytz.UTC).timestamp())
15
to_ = int(datetime(2018, 3, 31, 0, 0, 0, 0, tzinfo=pytz.UTC).timestamp())
16
17
stripe.api_key = os.getenv('STRIPE_SECRET_KEY')
18
19
response = stripe.Charge.list(
20
created={'gte': from_, 'lte': to_}
21
)
22
23
for charge in response['data']:
24
date = datetime.fromtimestamp(charge['created']).replace(hour=0, minute=0, second=0, microsecond=0)
25
26
entry = next((x for x in repartition if x['label'] == date), None)
27
if entry is None:
28
values.append({'label': date, 'values': {'value': 1}]})
29
else:
30
entry['values']['value'] += 1
31
32
res = {
33
'data': {
34
'attributes': {
35
'value': values,
36
},
37
'type': 'stats',
38
'id': uuid.uuid4()
39
}}
40
41
return JsonResponse(res, safe=False)
Copied!

Objective API-based Chart

Creating an Objective Smart Chart means you'll be fetching your data from an external API endpoint:
This endpoint must return data with the following format:
1
{
2
value: {
3
value: xxxx,
4
objective: yyyy
5
}
6
}
Copied!
Here's how you could implement it:
SQL
Mongodb
Rails
Django
/routes/dashboard.js
1
// [...]
2
const Liana = require('forest-express-sequelize');
3
4
// [...]
5
6
router.post('/stats/some-objective', (req, res) => {
7
// fetch your data here (a promise must be returned)
8
.then(() => {
9
let json = new Liana.StatSerializer({
10
value: {
11
value: fetchedValue,
12
objective: fetchedObjective
13
}
14
}).perform();
15
16
res.send(json);
17
}
18
}
Copied!
/routes/dashboard.js
1
// [...]
2
const Liana = require('forest-express-mongoose');
3
4
// [...]
5
6
router.post('/stats/some-objective', (req, res) => {
7
// fetch your data here (a promise must be returned)
8
.then(() => {
9
let json = new Liana.StatSerializer({
10
value: {
11
value: fetchedValue,
12
objective: fetchedObjective
13
}
14
}).perform();
15
16
res.send(json);
17
}
18
}
Copied!
/config/routes.rb
1
...
2
3
namespace :forest do
4
post '/stats/some-objective' => 'customers#some_objective'
5
end
6
7
...
Copied!
/app/controller/forest/your-model-controller.rb
1
...
2
3
def some_objective
4
# fetch your data here
5
stat = ForestLiana::Model::Stat.new({
6
value: {
7
value: 10, # the fetched value
8
objective: 678 # the fetched objective
9
}
10
})
11
render json: serialize_model(stat)
12
end
13
14
...
Copied!
app/urls.py
1
from django.urls import path
2
from django.views.decorators.csrf import csrf_exempt
3
4
from . import views
5
6
app_name = 'app'
7
urlpatterns = [
8
path('/stats/some-objective', csrf_exempt(views.SomeObjectiveView.as_view()), name='stats-some-objective')
9
]
Copied!
app/views.py
1
import uuid
2
3
from django.views import generic
4
from django.http import JsonResponse
5
6
7
class SomeObjectiveView(generic.ListView):
8
9
def post(self, request, *args, **kwargs):
10
# fetch your data here
11
12
res = {
13
'data': {
14
'attributes': {
15
'value': {
16
'value': 10, # the fetched value
17
'objective': 678 # the fetched objective
18
}
19
},
20
'type': 'stats',
21
'id': uuid.uuid4()
22
}}
23
Copied!
Last modified 2mo ago