Commit 540ebdd5 authored by Vikas's avatar Vikas

Added 2 example apps

parents
Integrate with openweathermap.org to see Contact's location in Open Street maps and climate details.
Add a Summary view widget in Contacts module and show maps and other weather details after fetching details from openweathermap.org.
Save the API Key from openweathermap.org in vtiger so API's can use it to connect and fetch details.
Sign up on https://openweathermap.org/ to get free api access to weather data.
For more details on the API see https://openweathermap.org/current
Access API after you sign up from https://home.openweathermap.org/api_keys
For this integration we will make use of Api Designer and Module Designer.
Api Designer will be used to add api and connecting to openweathermap.
Module Designer will be used to register for Wdiget in Contacts module.
1. First we will add a new API from Api Designer with name "get_weather".
2. Go to Menu > Platform > Api Designer > Add Api, select module and provide a unique api name(get_weather).
3. For your reference the code is added in get_weather.xml file.
4. Now we need to add a widget and also store api key in Contacts module, for this we need to register a vtiger component using VTAP.Component.Register client api.
5. Create a TAP Script from Menu > Module Designer, select Contacts modules. On the right side you will have option to add under TAP Script.
6. Add New Resource popup will show, there give a name for your script(RegisterWeatherWidget for example). Select the module where your widget should appear.
7. Default component name will be created in the VTAP script with name Contacts_Component_RegisterWeatherWidget (if Contacts module is selected).
8. In created function register for Summary widget and modal popup to save api key. (see RegisterWeatherWidget.js file for detailed explanation)
9. If you open a contact which has mailing city then it will show map and current weather details in the widget.
10. You can access get_weather api from javascript using VTAP.CustomApi object. Then you can generate a html using the data from the api.
Note : Adding any external api's or javascript resource needs to be registered.
For Api's you can register in list page of Api Designer, under Settings > Allowed Domains.
For Javascript or style libraries, you can register from Module Designer > Settings > Allowed Domains.
var Contacts_Component_RegisterWeatherWidget = VTAP.Component.Core.extend({
//all the component registration is done in created function
created() {
//this wil register for a link in List page settings accessible only for admins, this will be used to save api key.
VTAP.Component.Register('LIST_ADVANCED_SETTING',
{
name:'Weather Settings',
clickHandler:() => {
//show modal popup, search WeatherSettings component of Contacts module (Contacts_Component_WeatherSettings)
VTAP.Utility.ShowModal({component:VTAP.Component.Load("WeatherSettings","Contacts")});
}
});
//register for widget in summary view
VTAP.Component.Register('DETAIL_SUMMARY_WIDGET', {}, VTAP.Component.Load('WeatherWidget','Contacts'));
//add external javascript and style library
VTAP.Resource.Require('https://unpkg.com/[email protected]/dist/leaflet.js');
VTAP.Resource.Require('https://unpkg.com/[email protected]/dist/leaflet.css','style');
}
});
//This will be called by VTAP.Utility.ShowModal to show popup and save api key.
var Contacts_Component_WeatherSettings = VTAP.Component.Core.extend({
//store any internal data that will be used in html template
data() {
return {
key : ''
}
},
created() {
//request the api key that was stored and assign it to internal data variable key, this will be used in html template to display.
VTAP.AppData.Get('Contacts', {"data_key":"weather_apikey"}, (error, success) => {
if(success && success.length) {
this.key = success[0]['data_value'];
}
});
},
//functions that can be called from template in this component
methods : {
save() {
//this will save api key and will be accessible only for admins
//to use this key in API Designer, you need to access it with $apps.$app.Contacts.weather_apikey
//$apps.$app will access data that is saved from VTAP.AppData.Save api, next is module name and final is data_key.
VTAP.AppData.Save('Contacts', {"data_key":"weather_apikey", "data_value":this.key, onlyadmin : true}, (error, success) => {
if(success) {
VTAP.Utility.ShowSuccessNotification();
}
});
}
},
//html template which will be rendered, using bootstrap vue components like b-modal.
template:
`<b-modal size="md" title="Weather API Key" @ok="save()" :ok-title="translate('LBL_SAVE')">
<div class='p-4'>
<div class='align-items-center d-flex justify-content-center'>
<div class='col-3'>API Key</div>
<div class='col-9'><input type="text" class='form-control' v-model="key"></div>
</div>
</div>
</b-modal>`
});
//this component will be called for DETAIL_SUMMARY_WIDGET registration.
var Contacts_Component_WeatherWidget = VTAP.Component.Core.extend({
data() {
return {
data : ''
}
},
//when the Component html is mounted on DOM this function will be called.
mounted() {
//Request to get current record (if you have opened detail page of a record
VTAP.Detail.Record().then( (record) => {
//this will call get_weather custom api we added from Api Designer.
VTAP.CustomApi.Get('get_weather', {'city' : record.mailingcity},
(error, response) => {
if(response && response.content) {
this.data = JSON.parse(response.content);
setTimeout(() => {
this.showMap();
}, 5000);
}
});
});
},
methods : {
//shows open street map based on the lat lng received from openweather api
showMap() {
var element = document.getElementById('osm-map');
var map = L.map(element);
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
// set's GPS coordinates.
var target = L.latLng(this.data.coord.lat, this.data.coord.lon);
map.setView(target, 13);
L.marker(target).addTo(map);
},
getDisplayTime(timestamp) {
return moment.unix(timestamp).format('hh:mm A');
},
getDisplayTemperature(temp) {
return Math.round(temp - 273.15);
}
},
//html rendered in summary widget
template:
`<div class='p-2 bg-white'>
<div class='p-2'>Weather details</div>
<div class='row' v-if="data">
<div class='col-6'>
<div id="osm-map" style="height:300px;"></div>
</div>
<div class='col-6'>
<table class='table table-striped'>
<tr><th>Properties</th><th>Value</th></tr>
<tr><td>Sunrise Time</td><td>{{getDisplayTime(data.sys.sunrise)}}</td></tr>
<tr><td>Sunset Time</td><td>{{getDisplayTime(data.sys.sunset)}}</td></tr>
<tr><td>Max Temp</td><td>{{getDisplayTemperature(data.main.temp_min)}} C</td></tr>
<tr><td>Min Temp</td><td>{{getDisplayTemperature(data.main.temp_min)}} C</td></tr>
<tr><td>Weather description</td><td>{{data.weather[0].description}}</td></tr>
</table>
</div>
</div>
</div>`
});
<?xml version="1.0"?>
<api method="get">
<rest method="get">
<url appid="$apps.$app.Contacts.weather_apikey" city="@city">
<![CDATA[https://api.openweathermap.org/data/2.5/weather?appid=$appid&q=$city]]>
</url>
</rest>
</api>
Integrate with Slack using VTAP.Oauth authentication.
Purpose : Give users option to send vtiger record details to a slack channel.
Details :
1. Provide button in detail page of a record to send details to a channel.
2. When the user clicks the button, we will show public channels available.
3. If authentication is not done, then it will prompt you to add oauth client details.
4. Create a oauth app(https://api.slack.com/apps?new_app=1), and enable channels:read from User Token Scopes and chat:write, chat:write.public scope from Bot Token Scopes.
5. Registered app in Slack will provide Client ID and Client Secret, copy them in the oauth client popup.
6. Add https://slack.com/oauth/v2/authorize for Auth URL and https://slack.com/api/oauth.v2.access for Token URL. Add "channels:read,chat:write" for scope.
7. After client details are saved, it will shown Slack screen for granting permission.
We need to add 2 api's in API Designer.
1. get_slack_channels.xml file has API details which will connect Slack using OAuth tokens and retrieves Channels.
2. send_record_to_slack.xml sends record details to particular Slack channel
Add a new extension module from Module Designer called SlackIntegration.
We need to add TAP Script with name "Register".
Register.js file has the contents of the script.
This script will add a widget in detail header page, two action link in more actions(3 dots in detail page).
The script is added with comments for every step for better understanding.
var vtcmslackintegration_Component_Register = VTAP.Component.Core.extend({
created() {
//Register Slack widget in detail page header, last parameter is to restrict it to show for Contacts module only.
VTAP.Component.Register('DETAIL_HEADER_WIDGET', {}, App.loadComponent('SlackButton', 'vtcmslackintegration'), {module:'Contacts'});
//Register detail more action for removing Slack Client details
VTAP.Component.Register('DETAIL_MORE_ACTION_ITEM', {}, App.loadComponent('SlackRemoveClient', 'vtcmslackintegration'), {module:'Contacts'});
////Register detail more action for removing Slack Tokens
VTAP.Component.Register('DETAIL_MORE_ACTION_ITEM', {}, App.loadComponent('SlackRevokeToken', 'vtcmslackintegration'), {module:'Contacts'});
}
});
//Slack button component in detail page header
var vtcmslackintegration_Component_SlackButton = VTAP.Component.Core.extend({
data() {
return {
channelloading : false,
channels : [],
}
},
methods : {
/**
* Function used to retrieve Slack channels
*/
getChannels() {
this.channelloading = true;
//calling custom api added in Api Designer
VTAP.CustomApi.Get('get_slack_channels', {}, (error, success) => {
if(success && success.content) {
let content = JSON.parse(success.content);
if(content['ok'] == false && (content['error'] == 'account_inactive' || content['error'] == 'not_authed')) {
//request for client app and tokens if not provided using Authorize api
VTAP.OAuth.Authorize("slackintegration","Contacts", () => {
this.channelloading = false;
this.getChannels();
});
return;
}
let response = JSON.parse(success.content);
this.channels = response.channels;
} else {
if(error && error.message) {
VTAP.Utility.ShowErrorNotification(error.message);
} else {
VTAP.Utility.ShowErrorNotification();
}
}
this.channelloading = false;
});
},
/**
* Function used to send Slack message
*/
sendToChannel(channel) {
VTAP.Modules.GetModule('Contacts').then(moduleModel => {
//record details needed to contruct Slack block
VTAP.Detail.Record().then( record => {
//construct JSON block that should be sent to Slack
let blocks = this.generateSlackBlock(record, moduleModel);
//call custom api added in Api Designer
VTAP.CustomApi.Post('send_record_to_slack',
{
'channel':channel.id,
'blocks':JSON.stringify(blocks),
'text':this.generateSlackText(record)
},
(error, success) => {
if(success && success.content) {
let response = JSON.parse(success.content);
if(response && response['ok']) VTAP.Utility.ShowSuccessNotification();
} else {
if(error && error.message) {
VTAP.Utility.ShowErrorNotification(error.message);
} else {
VTAP.Utility.ShowErrorNotification();
}
}
});
});
});
},
/**
* Function generates detail view url
*/
getDetailViewURL(record) {
let url = $('base').attr('href');
return url + 'view/detail?id=' + record.id + '&module=' + record._moduleName;
},
generateSlackText(record) {
return record['label'] + ' is shared in the channel';
},
/**
* Function generates SLack JSON
*/
generateSlackBlock(record, moduleModel) {
let detailURL = this.getDetailViewURL(record);
let text = [];
let header = '*'+ record['label'] + '*';
text.push({'type':'section','text':{'type':'mrkdwn','text':header}});
let fields = moduleModel.fields;
let displayFields = [];
for(let fieldname in fields) {
if(fields[fieldname].isViewable && (fields[fieldname].isHeaderField || fields[fieldname].isNameField) && record[fieldname]) {
displayFields.push({'type':'mrkdwn','text':'*' + fields[fieldname].translated_fieldlabel + '*:\n ' + record[fieldname]})
}
}
text.push({'type':'section','fields':displayFields});
let actions = {'type':'actions','elements':[{'type':'button','style':'primary','url':detailURL, 'text':{'text':'View in CRM','type':'plain_text'}}]};
text.push(actions);
return text;
}
},
//html template
template:
`<b-dropdown @show="getChannels()">
<template slot="button-content">
<img v-if="!channelloading" class='bg-white mr-1' src="https://a.slack-edge.com/80588/marketing/img/meta/favicon-32.png" width="16px" height="16px">
Send to Slack
</template>
<div class='pl-2 text-grey-2'>Channels</div>
<b-dropdown-item-button href="#" v-for="channel,i in channels" :key="i" @click="sendToChannel(channel)">{{channel.name}}</b-dropdown-item-button>
<b-spinner v-if="channelloading" style="width: 16px; height: 16px;" label="loading channels.."></b-spinner>
</b-dropdown>`
});
//This is added in detail view more actions, handles removing Slack Client details.
var vtcmslackintegration_Component_SlackRemoveClient = VTAP.Component.Core.extend({
methods : {
click() {
//This removes Slack client details, notice the first parameter which is the identified name of the service which was used to save.
VTAP.OAuth.DeleteAuthClientDetails('slackintegration','Contacts');
VTAP.Utility.ShowSuccessNotification('Slack client details removed');
}
},
template:
`<b-dropdown-item @click="click()" class="dropdown-item px-3 d-flex align-items-center height-37px">
<span class="moduleIcon">
<img class='bg-white mr-1' src="https://a.slack-edge.com/80588/marketing/img/meta/favicon-32.png" width="16px" height="16px">
</span>
<span class="text-truncate text-danger">Remove Slack Client</span>
</b-dropdown-item>`
});
//This is added in detail view more actions, handles removing Slack Tokens
var vtcmslackintegration_Component_SlackRevokeToken = VTAP.Component.Core.extend({
methods : {
click() {
VTAP.OAuth.Revoke('slackintegration','Contacts');
VTAP.Utility.ShowSuccessNotification('Slack tokens removed');
}
},
template:
`<b-dropdown-item @click="click()" class="dropdown-item px-3 d-flex align-items-center height-37px">
<span class="moduleIcon">
<img class='bg-white mr-1' src="https://a.slack-edge.com/80588/marketing/img/meta/favicon-32.png" width="16px" height="16px">
</span>
<span class="text-truncate text-danger">Revoke Slack Token</span>
</b-dropdown-item>`
});
<?xml version="1.0"?>
<api method="get">
<rest method="get">
<url>https://slack.com/api/conversations.list</url>
<auth>
<oauth type="vtap" service="slackintegration" module="Contacts"></oauth>
</auth>
</rest>
</api>
<?xml version="1.0"?>
<api method="post">
<rest method="post">
<url>https://slack.com/api/chat.postMessage</url>
<auth>
<oauth type="vtap" service="slackintegration" module="Contacts"></oauth>
</auth>
<parameters>
<parameter name='channel' value='@channel'></parameter>
<parameter name='blocks' value='@blocks'></parameter>
<parameter name='text' value='@text'></parameter>
</parameters>
</rest>
</api>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment