diff options
author | jsseidel <jsseidel@fastmail.com> | 2017-09-28 14:31:07 -0400 |
---|---|---|
committer | jsseidel <jsseidel@fastmail.com> | 2017-09-28 14:34:01 -0400 |
commit | a3b65e405278eb4b54de869a19b66ed9338dd1a6 (patch) | |
tree | 51b694ca9e585bcd76cf6c39ea95974a949d8beb /docs/tutorials/portal-sdk | |
parent | 51d83152697da4f2ef2242471ee43f36e6b64300 (diff) |
Added tutorial for portal-sdk apps
Added a tutorial for how to create a basic app
using the portal sdk.
Change-Id: I52783748760501e57751e19d1eb586d21cbef0d9
Issue-Id: PORTAL-100
Signed-off-by: jsseidel <jsseidel@fastmail.com>
Diffstat (limited to 'docs/tutorials/portal-sdk')
-rw-r--r-- | docs/tutorials/portal-sdk/architecture.rst | 18 | ||||
-rw-r--r-- | docs/tutorials/portal-sdk/creating.rst | 138 | ||||
-rw-r--r-- | docs/tutorials/portal-sdk/dynamic-content.rst | 30 | ||||
-rw-r--r-- | docs/tutorials/portal-sdk/google-charts.rst | 129 | ||||
-rwxr-xr-x | docs/tutorials/portal-sdk/img/addmenu.png | bin | 0 -> 9010 bytes | |||
-rw-r--r-- | docs/tutorials/portal-sdk/img/menus.png | bin | 0 -> 147244 bytes | |||
-rw-r--r-- | docs/tutorials/portal-sdk/img/newmenuitem.png | bin | 0 -> 78406 bytes | |||
-rw-r--r-- | docs/tutorials/portal-sdk/index.rst | 23 | ||||
-rw-r--r-- | docs/tutorials/portal-sdk/intro.rst | 9 | ||||
-rw-r--r-- | docs/tutorials/portal-sdk/pulling-db-data.rst | 142 | ||||
-rw-r--r-- | docs/tutorials/portal-sdk/setting-up-db.rst | 102 | ||||
-rw-r--r-- | docs/tutorials/portal-sdk/setting-up.rst | 137 | ||||
-rw-r--r-- | docs/tutorials/portal-sdk/wrapping-up.rst | 4 | ||||
-rw-r--r-- | docs/tutorials/portal-sdk/your-angularjs-app.rst | 282 |
14 files changed, 1014 insertions, 0 deletions
diff --git a/docs/tutorials/portal-sdk/architecture.rst b/docs/tutorials/portal-sdk/architecture.rst new file mode 100644 index 00000000..ba97157b --- /dev/null +++ b/docs/tutorials/portal-sdk/architecture.rst @@ -0,0 +1,18 @@ +Application architecture +============================================ + +When building the tutorial app with the ONAP Portal SDK, you'll be working with: + +1. `Spring Framework`_: You'll build a Spring Framework controller, which will handle your database queries and provide the top-level access to your application. +2. AngularJS_: Support for AngularJS is built into the SDK. AngularJS is a popular and powerful framework that lets developers create dynamic client-side web applications. E.g. Gmail. Unlike server-side frameworks like PHP, almost all GUI interaction is handled by the client in JavaScript. +3. `Bootstrap UI`_: Bootstrap is a front-end framework that makes designing web pages easier by incorporating extensive CSS. +4. `Google Charts`_: Google charts makes including graphs in your applications easier. +5. Gridster_: Although not covered in this tutorial, the ONAP Portal SDK also comes with built-in support for the Gridster JavaScript library, which lets you produce pages with draggable and resizeable layouts from elements spanning multiple columns. + +Although the learning curve may seem daunting, the most difficult aspect of creating ONAP Portal SDK web applications is understanding how all the pieces fit together. The various frameworks themselves are not hard to understand. However, how they interact with one another isn't always obvious. If you have a basic grasp of Java, JavaScript, HTML, and CSS, you'll have no trouble. + +.. _Spring Framework: https://projects.spring.io/spring-framework/ +.. _AngularJS: https://angularjs.org/ +.. _Bootstrap UI: http://www.bootstrap-ui.com/ +.. _Google Charts: https://developers.google.com/chart/ +.. _Gridster: http://dsmorse.github.io/gridster.js/ diff --git a/docs/tutorials/portal-sdk/creating.rst b/docs/tutorials/portal-sdk/creating.rst new file mode 100644 index 00000000..0285846c --- /dev/null +++ b/docs/tutorials/portal-sdk/creating.rst @@ -0,0 +1,138 @@ +Creating a new application +========================== + +Our tutorial application will consist of two parts: + +1. Java code: We'll create a controller (in reality you might need to create many, depending on how complex your application is) that will handle many of our web requests. Most importantly, it will handle the top-level access request. +2. Web code: The web code consists of all the files that Tomcat serves when requests are made for our application. Files like HTML, CSS, JavaScript, images, and so on. + +If you're working with code command-line only (e.g. vim), you'll want to create symlinks to both parts of the code, :code:`java` and :code:`web`. While debugging, you'll frequently move between the two halves. + +Java code location +------------------ + +Our Java code will reside here: + +:: + + sdk/ecomp-sdk/epsdk-app-os/src/main/java/org/openecomp/myapp + + +Create the :code:`myapp` directory. + +Web code location +----------------- + +Our web code will reside here: + +:: + + sdk/ecomp-sdk/epsdk-app-os/src/main/webapp/app/fusion/scripts/myapp + +Create the :code:`myapp` directory. + +Inside this directory, we'll add our first subpage: + +:: + + sdk/ecomp-sdk/epsdk-app-os/src/main/webapp/app/fusion/scripts/myapp/myfirstpage + +Create the :code:`myfirstpage` directory. + +Spring controllers +------------------ + +We need a controller to handle the top-level access requests (e.g. :code:`localhost:8080/epsdk-app-os/somepage`). For every :code:`somepage` our app needs, we'll need to either designate an existing controller to handle the request or create a new one. + +Why would you need to create a new one? + +The controller is where you'll set up your database access, read from database tables, update database tables, and so on. It may make sense to use different controllers for different :code:`somepage` functions to increase readability and make things easier when others need to maintain the code. + +A basic controller +~~~~~~~~~~~~~~~~~~ + +Copy the following basic controller code into your Java code location as :code:`MyAppController.java` + +.. code-block:: java + + package org.openecomp.myapp; + + import java.util.HashMap; + import java.util.ArrayList; + import java.util.Map; + import java.util.List; + import java.io.IOException; + + import java.beans.PropertyVetoException; + + import org.springframework.stereotype.Controller; + import org.springframework.web.bind.annotation.RequestMapping; + import org.springframework.web.bind.annotation.RequestMethod; + import org.springframework.web.bind.annotation.PathVariable; + import org.springframework.stereotype.Repository; + import org.springframework.beans.factory.annotation.Autowired; + import org.springframework.web.servlet.ModelAndView; + import org.springframework.jdbc.core.JdbcTemplate; + + import javax.servlet.http.HttpSession; + import javax.servlet.http.HttpServletRequest; + import javax.servlet.http.HttpServletResponse; + import javax.sql.DataSource; + import javax.annotation.PostConstruct; + + import com.mchange.v2.c3p0.ComboPooledDataSource; + + import org.json.JSONObject; + import org.json.JSONArray; + + import org.openecomp.portalsdk.core.controller.RestrictedBaseController; + import org.openecomp.portalsdk.core.util.SystemProperties; + import org.openecomp.portalsdk.core.domain.User; + + @Controller + @Repository + @RequestMapping("/") + public class MyAppController extends RestrictedBaseController { + /////////////////////////////////////////////////////////////////////////////// + // Constructor + // + public MyAppController() { + super(); + } + + /////////////////////////////////////////////////////////////////////////////// + // Handle 'myfirstpage' requests + // + @RequestMapping(value = { "/myfirstpage" }, method = RequestMethod.GET) + public ModelAndView myfirstpage(HttpServletRequest request) { + final String defaultViewName = null; + return new ModelAndView(defaultViewName); + } + } + +.. _definitions.xml: + +Request routing via definitions.xml +----------------------------------- + +In order for the framework to route requests for :code:`myfirstpage` correctly, we'll need to create an entry in :code:`sdk/ecomp-sdk/epsdk-app-os/src/main/webapp/WEB-INF/defs/definitions.xml` that looks like this: + +:: + + <definition name="myfirstpage" template="/app/fusion/scripts/myapp/myfirstpage/myfirstpage.html" /> + +Then, add the following to :code:`sdk/ecomp-sdk/epsdk-app-os/src/main/webapp/app/fusion/scripts/myapp/myfirstpage/myfirstpage.html`: + +.. code-block:: html + + <html> + <body> + <p>It worked!</p> + </body> + </html> + +Now, build and install your application as before. If everything worked, you should see `It worked!` in your browser window when you visit myfirstpage_ after logging in. + +When the request from the browser comes in, the framework creates a mapping from :code:`myfirstpage` to the MyAppController, which in turn maps your definition name to a particular template. Soon, we'll fill in that template to do more interesting things. + +.. _myfirstpage: http://localhost:8080/epsdk-app-os/myfirstpage diff --git a/docs/tutorials/portal-sdk/dynamic-content.rst b/docs/tutorials/portal-sdk/dynamic-content.rst new file mode 100644 index 00000000..aa8c067a --- /dev/null +++ b/docs/tutorials/portal-sdk/dynamic-content.rst @@ -0,0 +1,30 @@ +Dynamic content +=============== + +Now, we'll make our new application dynamic by allowing user interaction. To do this, we'll let the user select between "download" and "upload" charts with an HTML select/option menu. + +Creating the Angularized select/option menu +------------------------------------------- + +Add the following just above our Google Chart placeholder in :code:`sdk/ecomp-sdk/epsdk-app-os/src/main/webapp/app/fusion/scripts/myapp/myfirstpage/template.html`: + +.. code-block:: html + + <select ng-model="state.direction" ng-change="getChartData(state.direction)" ng-options="direction for direction in ['download', 'upload']"></select> + +The :code:`state.direction` value for the :code:`ng-model` attribute tells AngularJS that the :code:`state.direction` should hold the current value of the selected option. We saw it earlier in our controller: + +.. code-block:: javascript + + $scope.state = { + . + . + // Holds the selected direction from the select/options control + direction: "download" + . + . + }; + +How this works is simple. Using the options defined in :code:`ng-options`, AngularJS creates a select/option button. When the user selects an option, the value is stored in :code:`ng-model`. If the option changes, AngularJS calls our :code:`getChartData` function with the selected option as an argument. + +Compile, install, and try it out. diff --git a/docs/tutorials/portal-sdk/google-charts.rst b/docs/tutorials/portal-sdk/google-charts.rst new file mode 100644 index 00000000..859849c5 --- /dev/null +++ b/docs/tutorials/portal-sdk/google-charts.rst @@ -0,0 +1,129 @@ +Google Charts +============= + +Now that we've established our database connection and can request and retrieve data from our tables, we can focus on our web application. + +Installing +---------- + +First, we'll need to grab the Angular-ized Google Charts. Do this: + +:: + + cd sdk/ecomp-sdk/epsdk-app-os/src/main/webapp/app/fusion/scripts/myapp/myfirstpage + wget https://raw.githubusercontent.com/angular-google-chart/angular-google-chart/development/ng-google-chart.min.js + +.. note:: The "min" in the file name indicates that any and all unnecessary spaces, newlines, etc. have been removed from the file to "min"imize the size. + +Now, add a reference to Google Charts in your :code:`sdk/ecomp-sdk/epsdk-app-os/src/main/webapp/app/fusion/scripts/myapp/myfirstpage/myfirstpage.html` file: + +:: + + . + . + . + <!-- Page specific items --> + <script src="app/fusion/scripts/myapp/myfirstpage/controller.js"></script> + <script src="app/fusion/scripts/myapp/myfirstpage/route.js"></script> + <script src="app/fusion/scripts/myapp/myfirstpage/data-service.js"></script> + <!-- Google Charts --> + <script src="app/fusion/scripts/myapp/myfirstpage/ng-google-chart.min.js"></script> + . + . + . + +Now, make sure we tell our app that we'll need to use this dependency by adding a reference to it in :code:`sdk/ecomp-sdk/epsdk-app-os/src/main/webapp/app/fusion/scripts/myapp/myfirstpage/app.js`: + +.. code-block:: javascript + + var appDS2=angular.module("abs", ['ngRoute', 'ngMessages','modalServices', 'ngCookies', 'b2b.att','gridster','ui.bootstrap','ui.bootstrap.modal','googlechart']); + +Configuring our chart +--------------------- + +Change the initialization of the :code:`$scope.state` variable by using the following inside the init function in your :code:`sdk/ecomp-sdk/epsdk-app-os/src/main/webapp/app/fusion/scripts/myapp/myfirstpage/controller.js` file: + +.. code-block:: javascript + + $scope.state = { + // Holds a message string for testing + message: "Hello from myFirstPageController", + // Holds the desired direction of the chart + direction: "download", + chart: { + type: "LineChart", + data: { + cols: [{id: "dt", label: "Date", type: "date"}, + {id: "c1", label: "Bandwidth", type: "number"}, + {type: "string", role: "tooltip"}], + rows:[] // These change for every chart + }, + options: { + title: "", + hAxis: {title: "Date", format: "MM/dd/yyyy"}, + vAxis: {title: "Bandwidth (Mbps)", minValue: 0}, + colors: ['blue'], + defaultColors: ['blue'], + legend: {position: "top", maxLines: 2}, + isStacked: false, + pointSize: 2 + } + } + }; + +The configuration options are self-explanatory. Experiment with them to get a better idea of what's possible. + +Populating our chart +-------------------- + +Add the following function to your :code:`sdk/ecomp-sdk/epsdk-app-os/src/main/webapp/app/fusion/scripts/myapp/myfirstpage/controller.js` file: + +.. code-block:: javascript + + $scope.getChartData = function(direction) { + dataService.getChartData(encodeURI(direction)).then(function(rv) { + var arr = JSON.parse(angular.toJson(rv)); + + // Clear out our rows + $scope.state.chart.data.rows = []; + + for (var i=0; i<arr.length; i++) { + var t = arr[i].data_date.split(/[-]/); + var d = new Date(t[0], t[1]-1, t[2], 0, 0, 0); + var row = {}; + + row.c = [{v: d}, + {v: arr[i].speedmbps, f: "speed"}, + {v: arr[i].speedmbps + " Mbps"}]; + + $scope.state.chart.options.title = "Avg Bandwith in Mbps (" + direction + ")" + $scope.state.chart.data.rows.push(row); + } + }); + } + +When our call to :code:`getChartData` returns (and only then) do we populate the rows of our Google Chart. Each row in a Google Chart, as defined in our :code:`init` function, consists of a date, speed, and finally a special annotation called a "tooltip" that pops up a small window with some text whenever the user hovers over a data point. You can also use these special annotations to change the style of the point being displayed. See the `Google Charts`_ documentation for more info. + +One subtle but important piece of the code above is that we wrap :code:`direction` in :code:`encodeURI`. This ensures that the arguments passed in the HTTP request are acceptable and mean what you think they mean when they are caught and decoded in your Spring controller. That framework will automatically handle the decoding. + +Now, add a call to :code:`getChartData` in your :code:`init` function, just before the closing curly bracket: + +.. code-block:: javascript + + $scope.getChartData("download"); + +Updating the template +--------------------- + +Finally, we'll need to add a placeholder for the chart in :code:`sdk/ecomp-sdk/epsdk-app-os/src/main/webapp/app/fusion/scripts/myapp/myfirstpage/template.html`. Here's what it looks like: + +.. code-block:: html + + <div id="page-content" class="content" style="padding: 25px;"> + <p>{{state.message}}</p> + <div class="md-media-lg" align="center" google-chart chart="state.chart" style="height: 400px;"></div> + </div> + +Recompile, install, and try it out. + +.. _Google Charts: https://developers.google.com/chart/interactive/docs/points diff --git a/docs/tutorials/portal-sdk/img/addmenu.png b/docs/tutorials/portal-sdk/img/addmenu.png Binary files differnew file mode 100755 index 00000000..a7093d10 --- /dev/null +++ b/docs/tutorials/portal-sdk/img/addmenu.png diff --git a/docs/tutorials/portal-sdk/img/menus.png b/docs/tutorials/portal-sdk/img/menus.png Binary files differnew file mode 100644 index 00000000..bd05d5cd --- /dev/null +++ b/docs/tutorials/portal-sdk/img/menus.png diff --git a/docs/tutorials/portal-sdk/img/newmenuitem.png b/docs/tutorials/portal-sdk/img/newmenuitem.png Binary files differnew file mode 100644 index 00000000..b25314f3 --- /dev/null +++ b/docs/tutorials/portal-sdk/img/newmenuitem.png diff --git a/docs/tutorials/portal-sdk/index.rst b/docs/tutorials/portal-sdk/index.rst new file mode 100644 index 00000000..b37855cc --- /dev/null +++ b/docs/tutorials/portal-sdk/index.rst @@ -0,0 +1,23 @@ +.. Building ONAP Portal SDK Web Applications documentation master file, created by + sphinx-quickstart on Mon Aug 7 12:47:34 2017. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Building ONAP Portal SDK Web Applications +========================================= + +.. toctree:: + :maxdepth: 6 + :caption: Contents: + + intro + architecture + setting-up + creating + setting-up-db + your-angularjs-app + pulling-db-data + google-charts + dynamic-content + wrapping-up + diff --git a/docs/tutorials/portal-sdk/intro.rst b/docs/tutorials/portal-sdk/intro.rst new file mode 100644 index 00000000..9a117bf7 --- /dev/null +++ b/docs/tutorials/portal-sdk/intro.rst @@ -0,0 +1,9 @@ +Introduction +============ + +In this tutorial you'll be creating your own ONAP Portal SDK web application, starting with pulling the source code directly from ONAP's Portal SDK Gerrit repository. + +Your new web application will use the various frameworks that are included in the Portal SDK to display a simple but interactive application page that will let a user choose from two different charts to display. + +To do this, you'll learn how to setup and query a database in the backend as well as populate chart data for displaying. + diff --git a/docs/tutorials/portal-sdk/pulling-db-data.rst b/docs/tutorials/portal-sdk/pulling-db-data.rst new file mode 100644 index 00000000..4efeff56 --- /dev/null +++ b/docs/tutorials/portal-sdk/pulling-db-data.rst @@ -0,0 +1,142 @@ +Pulling DB data +=============== + +In the :ref:`connectionjava` section, we set up a connection to the :code:`ecomp_sdk` database. Now, we going to use our AngularJS controller (:code:`controller.js`) and data service (:code:`data-service.js`) to make an HTTP request to our Spring controller (:code:`MyAppController.java`), wait for the results, and map them into a Google Chart. + +AngularJS Promises +---------------------- + +"Promises" are a core feature of AngularJS, and whether you fully understand them or not, you will be using them in your applications. Promises use AJAX (Asynchronous JavaScript and XML -- you can also use JSON). When we make a call to a web server for some data, we don't want our application to become unresponsive while we wait for an answer. Therefore, :code:`$http.get` calls (more in a minute), return a Promise object rather than text from the web server. Then, we make sure that the Promise object does the right thing at the right time by assigning callback functions when the web server either returns results or fails to return a result. Something like this: + +.. code-block:: javascript + + var p = $http.get("http://somedomain.com/some_request"); + + p.success(function(result) { + console.log("The web server returned: " + result); + }); + + p.error(function(response, status) { + console.log("The web server returned:\n\tStatus: " + status + "\n\tError: " + response); + }); + +Here, AJAX (via the AngularJS module :code:`$http`) makes a request to :code:`somedomain.com/some_request`. Our JavaScript engine immediately then moves to assign anonymous functions as callbacks for success and error conditions. Then, when the web server finally returns a result, the callbacks are run. + +Data service +------------ + +Our special function in :code:`data-service.js` uses Promises. We make sure to execute code in the correct order by using the :code:`then` Promise function. + +Something like this: + +.. code-block:: javascript + + $scope.myFunction = function() { + dataService.getSomeData().then(function(rv) { + // Do something here. + }); + } + +Here, :code:`getSomeData` returns a Promise object. The :code:`then` function tells the JavaScript engine to execute the given anonymous function only after the request has completed. + +Technically, the :code:`then` function takes two functions as arguments. The first defines what to do upon success, and the second defines what to do upon failure. We omitted the failure argument above. + +Here is our :code:`data-service.js` code: + +.. code-block:: javascript + + appDS2.factory('dataService', function ($http, $q, $log) { + return { + // Service to return chart data + getChartData: function(direction) { + return $http.get("get_chart_data/" + direction + "/").then(function(response) { + if (typeof response.data === 'object' || typeof response.data === 'string') { + return response.data; + } + else { + return $q.reject(response.data); + } + }, function(response) { + return $q.reject(response.data); + }) + } + }; + }); + +The syntax of this function is not immediately obvious unless you are comfortable with JavaScript Promises and Deferreds. For a more complete explanation with examples, check out `this blog post <http://chariotsolutions.com/blog/post/angularjs-corner-using-promises-q-handle-asynchronous-calls/>`_. + +Essentially, our service definition is a super-concise JavaScript way to allow this from within our controller: + +.. code-block:: javascript + + dataService.getChartData(direction).then(function(rv) { + // Do something here + }); + +Behind the scenes, this makes an :code:`HTTP` request that looks like this: + +:code:`http://localhost:8080/epsdk-app-os/get_chart_data/<direction>/` + +where :code:`direction` is either "upload" or "download" and returns the result back to our controller as JSON text, which we'll convert into a JavaScript object for further processing. + +Modifying our Spring controller +------------------------------- + +Let's add a couple of functions to our Spring controller, :code:`MyAppController.java`: + +.. code-block:: java + + @RequestMapping(value = {"/get_chart_data/{direction}/"}, method = RequestMethod.GET) + public void getChartData(@PathVariable("direction") String direction, HttpServletRequest request, HttpServletResponse response){ + try { + Object a = _getChartData(direction); + response.getWriter().write(a.toString()); + } catch (IOException e) { + // Probably should do something here ;-) + } + } + + private Object _getChartData(String direction) { + ArrayList<JSONObject> allData = new ArrayList<JSONObject>(); + JdbcTemplate jdbcTempl = new JdbcTemplate(m_dataSources.get("myappdb")); + + // Check our parameter + if (!direction.equals("download") && !direction.equals("upload")) + direction = "download"; + } + + String query = "select data_date, speedmbps, direction from mock_data_avg_speed where direction='" + direction + "' order by data_date asc"; + + List<Map<String,Object>> out = jdbcTempl.queryForList(query); + for (Map<String,Object> row: out) { + JSONObject jo = new JSONObject(); + jo.put("data_date", row.get("data_date")); + jo.put("speedmbps", row.get("speedmbps")); + jo.put("direction", row.get("direction")); + allData.add(jo); + } + + return allData; + } + +Testing our changes +------------------- + +To test our database connection, first compile and install the war as in the :ref:`installingyourapp` section. Next, `login`_. Now try the `following URL`_: + +:: + + http://localhost:8080/epsdk-app-os/get_chart_data/download/ + +.. note:: Using the trailing '/' character can prevent confusion with AngularJS routing. It might not always be necessary, but it is good practice to use it in this context to prevent headaches later on. + +If everything went as planned, you should see: + +:: + + [{"speedmbps":40,"data_date":"2017-08-01","direction":"download"}, {"speedmbps":18,"data_date":"2017-08-02","direction":"download"}, {"speedmbps":25,"data_date":"2017-08-03","direction":"download"}, {"speedmbps":48,"data_date":"2017-08-04","direction":"download"}, {"speedmbps":49,"data_date":"2017-08-05","direction":"download"}, {"speedmbps":46,"data_date":"2017-08-06","direction":"download"}, {"speedmbps":35,"data_date":"2017-08-07","direction":"download"}] + +This is what makes JSON such a powerful tool. We'll take that JSON output and convert it into JavaScript objects in order to build our chart. + +.. _following URL: http://localhost:8080/epsdk-app-os/get_chart_data/download/ +.. _login: http://localhost:8080/epsdk-app-os/login.htm diff --git a/docs/tutorials/portal-sdk/setting-up-db.rst b/docs/tutorials/portal-sdk/setting-up-db.rst new file mode 100644 index 00000000..3e73fa17 --- /dev/null +++ b/docs/tutorials/portal-sdk/setting-up-db.rst @@ -0,0 +1,102 @@ +Setting up a database connection +================================ + +Most applications will need access to a database. In this tutorial, we'll connect to a database in order to pull data for displaying in a Google chart. + +Injecting data +-------------- + +First, let's generate some fake data to display. Create an sql file and populate it with the following: + +.. code-block:: sql + + use ecomp_sdk; + + create table MOCK_DATA_AVG_SPEED ( + data_date DATE, + speedmbps INT, + direction varchar(10) + ); + insert into MOCK_DATA_AVG_SPEED (data_date, speedmbps, direction) values ('2017-08-01', 40, 'download'); + insert into MOCK_DATA_AVG_SPEED (data_date, speedmbps, direction) values ('2017-08-02', 18, 'download'); + insert into MOCK_DATA_AVG_SPEED (data_date, speedmbps, direction) values ('2017-08-03', 25, 'download'); + insert into MOCK_DATA_AVG_SPEED (data_date, speedmbps, direction) values ('2017-08-04', 48, 'download'); + insert into MOCK_DATA_AVG_SPEED (data_date, speedmbps, direction) values ('2017-08-05', 49, 'download'); + insert into MOCK_DATA_AVG_SPEED (data_date, speedmbps, direction) values ('2017-08-06', 46, 'download'); + insert into MOCK_DATA_AVG_SPEED (data_date, speedmbps, direction) values ('2017-08-07', 35, 'download'); + insert into MOCK_DATA_AVG_SPEED (data_date, speedmbps, direction) values ('2017-08-01', 10, 'upload'); + insert into MOCK_DATA_AVG_SPEED (data_date, speedmbps, direction) values ('2017-08-02', 15, 'upload'); + insert into MOCK_DATA_AVG_SPEED (data_date, speedmbps, direction) values ('2017-08-03', 14, 'upload'); + insert into MOCK_DATA_AVG_SPEED (data_date, speedmbps, direction) values ('2017-08-04', 9, 'upload'); + insert into MOCK_DATA_AVG_SPEED (data_date, speedmbps, direction) values ('2017-08-05', 12, 'upload'); + insert into MOCK_DATA_AVG_SPEED (data_date, speedmbps, direction) values ('2017-08-06', 13, 'upload'); + insert into MOCK_DATA_AVG_SPEED (data_date, speedmbps, direction) values ('2017-08-07', 15, 'upload'); + +Now, run it. Something like this: + +:: + + mysql -p<passwd> -u<user> < mock_data.sql + +.. _connectionjava: + +Setting up a connection in Java +------------------------------- + +We'll need a place to store some data sources. In this case, we only need one, but your application might have more. Add the following member variable to your :code:`MyAppController.java` class: + +.. code-block:: java + + private HashMap<String,DataSource> m_dataSources; + +Don't forget to import the HashMap object: + +.. code-block:: java + + import java.util.HashMap; + +Now, we'll add a new private function, :code:`_getDataSources`: + +.. code-block:: java + + private HashMap<String,DataSource> _getDataSources() throws Exception { + HashMap<String,DataSource> dataSources = new HashMap<String,DataSource>(); + ComboPooledDataSource ds = new ComboPooledDataSource(); + try { + ds.setDriverClass(SystemProperties.getProperty("db.driver")); + ds.setJdbcUrl(SystemProperties.getProperty("db.connectionURL")); + ds.setUser(SystemProperties.getProperty("db.userName")); + ds.setPassword(SystemProperties.getProperty("db.password")); + ds.setMinPoolSize(Integer.parseInt(SystemProperties.getProperty(SystemProperties.DB_MIN_POOL_SIZE))); + ds.setMaxPoolSize(Integer.parseInt(SystemProperties.getProperty(SystemProperties.DB_MAX_POOL_SIZE))); + ds.setIdleConnectionTestPeriod(Integer.parseInt(SystemProperties.getProperty(SystemProperties.IDLE_CONNECTION_TEST_PERIOD))); + dataSources.put("myappdb", ds); + } + catch (Exception e) { + throw e; + } + + return dataSources; + } + +Notice that because we're piggy-backing our data to the ecomp_sdk database, we're borrowing a few properties as well. You can also add your own properties to :code:`sdk/ecomp-sdk/epsdk-app-os/src/main/webapp/WEB-INF/conf/system.properties` and use them. This allows you to create any number of connections and connection methods in your app. E.g.: + +.. code-block:: java + + ds.setDriverClass(SystemProperties.getProperty("db.some_other_driver")); + +Now, we need to add some code to our constructor so that the connection is set up when the controller is instantiated: + +.. code-block:: java + + public MyAppController() { + super(); + try { + this.m_dataSources = _getDataSources(); + } + catch (Exception e) { + // Probably a good idea to do something here ;-) + } + } + + diff --git a/docs/tutorials/portal-sdk/setting-up.rst b/docs/tutorials/portal-sdk/setting-up.rst new file mode 100644 index 00000000..6c0ece54 --- /dev/null +++ b/docs/tutorials/portal-sdk/setting-up.rst @@ -0,0 +1,137 @@ +Setting up +========== + +Dependencies +------------ + +In order to build Portal SDK applications on your machine, you'll need to install the following: + +1. OpenJDK 8 +2. Maven +3. MariaDB (v10.1) +4. Apache Tomcat (v8.5) + +Cloning the Portal SDK repository +--------------------------------- + +Clone the Portal SDK repository with git: + +:: + + git clone http://gerrit.onap.org/r/portal/sdk + +Building the base +----------------- + +Now, we'll build the base with maven: + +:: + + cd sdk/ecomp-sdk + mvn install + +Setting up the MariaDB database +------------------------------- + +Adding support for lowercase table names +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To add support for lowercase table names, make sure the following line in your :code:`/etc/mysql/my.cnf` file is present under the :code:`[mysqld]` section: + +:: + + lower_case_table_names = 1 + +If you made changes, you'll need to restart your MariaDB instance. Hint: + +:: + + sudo systemctl restart mariadb.service + + +Database setup +^^^^^^^^^^^^^^ + +Now, we set up the database and user privileges. Log into your MariaDB instance as the 'root' user and do the following: + +:: + + drop database if exists ecomp_sdk; + drop user if exists 'ecomp_sdk_user'@'localhost'; + + create database ecomp_sdk; + create user 'ecomp_sdk_user'@'localhost' identified by 'password'; + grant all privileges on ecomp_sdk.* to 'ecomp_sdk_user'@'localhost'; + +Adding tables +^^^^^^^^^^^^^ + +Next, we need to run several SQL statements in the repository: + +:: + + mysql -proot -uroot < sdk/ecomp-sdk/epsdk-app-common/db-scripts/EcompSdkDDLMySql_1707_Common.sql + mysql -proot -uroot < sdk/ecomp-sdk/epsdk-app-common/db-scripts/EcompSdkDMLMySql_1707_Common.sql + mysql -proot -uroot < sdk/ecomp-sdk/epsdk-app-os/db-scripts/EcompSdkDDLMySql_1707_OS.sql + mysql -proot -uroot < sdk/ecomp-sdk/epsdk-app-os/db-scripts/EcompSdkDMLMySql_1707_OS.sql + + +Using 'root' for both your MySQL username and password is just about the worst security policy imaginable. For anything other than temporary setups (very temporary), please choose reasonable user names and hard-to-guess passwords. + +Your project directory +------------------------------- + +Because you'll want your project to use the latest portal SDK code (retrieved via :code:`git pull`), you work directly in :code:`sdk/ecomp_sdk/epsdk-app-os`. + +Connecting your app to the backend database +------------------------------------------- + +We need to tell our app how to access the database tables we created above. Open :code:`sdk/ecomp-sdk/epsdk-app-os/src/main/webapp/WEB-INF/conf/system.properties` and change the following two lines to reflect the database user you set up above as well as your particular installation of MariaDB: + +:: + + db.connectionURL = jdbc:mysql://localhost:3306/ecomp_sdk + db.userName = ecomp_sdk_user + db.password = password + +Building your app +----------------- + +When you want to build your app: + +:: + + # First cd to the project directory + cd sdk/ecomp_sdk/epsdk-app-os + mvn clean package + +.. note:: You don't always have to :code:`clean`. Only use it when you want to clear out intermediate objects and build your entire project from scratch. + +.. _installingyourapp: + +Installing your app +------------------- + +To install your app, run the following commands, or better, create a script: + +:: + + # Shutdown tomcat + <tomcat install>/bin/shutdown.sh + rm -rf <tomcat install>/webapps/epsdk-app-os* + cp target/epsdk-app-os.war <tomcat install>/webapps/. + # Start tomcat + <tomcat install>/bin/startup.sh + +Viewing your app +---------------- + +Assuming you have installed Tomcat locally, for example in a vagrant VM with port forwarding, you can `access the app`_ in your normal browser: + +:: + + http://localhost:8080/epsdk-app-os/login.htm + +To log in, use user/password 'demo/demo'. + +.. _access the app: http://localhost:8080/epsdk-app-os/login.htm diff --git a/docs/tutorials/portal-sdk/wrapping-up.rst b/docs/tutorials/portal-sdk/wrapping-up.rst new file mode 100644 index 00000000..72a9482e --- /dev/null +++ b/docs/tutorials/portal-sdk/wrapping-up.rst @@ -0,0 +1,4 @@ +Wrapping up +=========== + +AngularJS and the ONAP Portal SDK are powerful tools, and we've only seen the bare minimum of their capabilities in this tutorial. Hopefully, you now have enough information to jump in and start building your own Portal apps. diff --git a/docs/tutorials/portal-sdk/your-angularjs-app.rst b/docs/tutorials/portal-sdk/your-angularjs-app.rst new file mode 100644 index 00000000..f97d6938 --- /dev/null +++ b/docs/tutorials/portal-sdk/your-angularjs-app.rst @@ -0,0 +1,282 @@ +Your AngularJS app +========================= + +Now that we've established a database connection, it's time to work on the web side of your app. To start, we'll create some files in :code:`sdk/ecomp-sdk/epsdk-app-os/src/main/webapp/app/fusion/scripts/myapp/myfirstpage`. + +myfirstpage.html +---------------- + +This is your landing page. Its purpose is to pull in all the JavaScript and CSS that your app might need as well as to set up your AngularJS app (:code:`app.js`) and your app's controller (:code:`controller.js`), data-services (:code:`data-service.js`), and routing information (:code:`route.js`) --- more on these in a moment. There is likely much that can be removed from this file (it is boilerplate copied from a sample Portal SDK app), but to save yourself headaches at first, just cut and paste all of it into the :code:`sdk/ecomp-sdk/epsdk-app-os/src/main/webapp/app/fusion/scripts/myapp/myfirstpage/myfirstpage.html` you created earlier in, redundancies and all: + +.. code-block:: html + + <!DOCTYPE html> + <!-- Single-page application for EPSDK-App using DS2 look and feel. --> + <html> + <head> + <meta charset="ISO-8859-1"> + <meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1" /> + + <title>Welcome</title> + + <!-- B2b Library --> + <link rel="stylesheet" type="text/css" href="app/fusion/external/b2b/css/b2b-angular/b2b-angular.css"> + <link rel="stylesheet" type="text/css" href="app/fusion/external/b2b/css/b2b-angular/font_icons.css"> + + <!-- icons in open source --> + <link rel="stylesheet" type="text/css" href="app/fusion/external/ds2/css/digital-ng-library/ionicons.css"> + <link rel="stylesheet" type="text/css" href="app/fusion/external/ds2/css/digital-ng-library/ecomp-ionicons.css"> + + <link rel="stylesheet" type="text/css" href="app/fusion/external/angular-bootstrap/ui-bootstrap-csp.css"> + <link rel="stylesheet" type="text/css" href="app/fusion/external/angular-gridster/dist/angular-gridster.min.css"> + <link rel="stylesheet" type="text/css" href="static/fusion/sample/css/scribble.css" /> + <link rel="stylesheet" type="text/css" href="static/fusion/sample/css/welcome.css" /> + + <link rel="stylesheet" type="text/css" href="app/fusion/styles/ecomp.css"> + + <!-- Common scripts --> + <script src="app/fusion/external/angular-1.4.8/angular.min.js"></script> + <script src="app/fusion/external/angular-1.4.8/angular-messages.js"></script> + <script src="app/fusion/external/angular-1.4.8/angular-touch.js"></script> + <script src="app/fusion/external/angular-1.4.8/angular-sanitize.js"></script> + <script src="app/fusion/external/angular-1.4.8/angular-route.min.js"></script> + <script src="app/fusion/external/angular-1.4.8/angular-cookies.min.js"></script> + <script src="app/fusion/external/jquery/dist/jquery.min.js"></script> + <script src="app/fusion/external/javascript-detect-element-resize/jquery.resize.js"></script> + <script src="app/fusion/external/angular-bootstrap/ui-bootstrap-tpls.min.js"></script> + <script src="app/fusion/external/angular-gridster/dist/angular-gridster.min.js"></script> + + <!-- EPSDK App scripts and common services --> + <!-- B2b Library --> + <script src="app/fusion/external/b2b/js/b2b-angular/b2b-library.min.js"></script> + <script src="app/fusion/scripts/DS2-services/ds2-modal/modalService.js"></script> + <script src="app/fusion/scripts/myapp/myfirstpage/app.js"></script> + + <script src="app/fusion/scripts/DS2-services/userInfoServiceDS2.js"></script> + <script src="app/fusion/scripts/DS2-services/headerServiceDS2.js"></script> + <script src="app/fusion/scripts/DS2-services/leftMenuServiceDS2.js"></script> + <script src="app/fusion/scripts/DS2-services/manifestService.js"></script> + + <script src="app/fusion/scripts/DS2-directives/footer.js"></script> + <script src="app/fusion/scripts/DS2-directives/ds2Header.js"></script> + <script src="app/fusion/scripts/DS2-directives/ds2LeftMenu.js"></script> + <script src="app/fusion/scripts/DS2-directives/b2b-leftnav-ext.js"></script> + <script src= "app/fusion/scripts/DS2-services/userInfoServiceDS2.js"></script> + + <!-- Page specific items --> + <script src="app/fusion/scripts/myapp/myfirstpage/controller.js"></script> + <script src="app/fusion/scripts/myapp/myfirstpage/route.js"></script> + <script src="app/fusion/scripts/myapp/myfirstpage/data-service.js"></script> + + <style> + .controls { + margin-bottom: 20px; + } + .page-header { + margin-top: 20px; + } + ul { + list-style: none; + } + .box { + height: 100%; + border: 1px solid #ccc; + background-color: #fff; + position: relative; + overflow: hidden; + } + .box-header { + background-color: #eee; + padding: 0px 0px 0px 0px; + margin-bottom: -25px; + cursor: move; + position: relative; + } + .box-header h3 { + margin-top: 0px; + display: inline-block; + } + .box-content { + padding: 10px; + display:block; + height: 100%; + position: relative; + overflow-x:auto; + overflow-y:auto; + } + .box-header-btns { + top: 15px; + right: 10px; + cursor: pointer; + position: absolute; + } + .gridster { + border: none; + position:relative; + } + .box-content .box-content-frame{ + } + .box table{ + border:none; + display:block; + } + .box table tr{ + line-height:20px; + } + .box table th{ + border:none; + line-height:20px; + } + .menu-container{ + margin-top:0px + } + .handle-e { + width:3px; + } + </style> + </head> + <body class="appBody" ng-app="abs"> + <!-- commented the header for now to avoid duplicate headers on portal --> + <div ds2-Header class="header-container" ></div> + <div ds2-menu id="menuContainer" class="menu-container"></div> + <div ng-view id="rightContentProfile" class="content-container"></div> + <div ds2-Footer class="footer-container"></div> + </body> + </html> + +app.js +------ + +:code:`sdk/ecomp-sdk/epsdk-app-os/src/main/webapp/app/fusion/scripts/myapp/myfirstpage/app.js` contains a single line: + +.. code-block:: javascript + + var appDS2=angular.module("abs", ['ngRoute', 'ngMessages','modalServices', 'ngCookies', 'b2b.att','gridster','ui.bootstrap','ui.bootstrap.modal']); + +Don't worry too much about the particulars here. Just know that the list of strings are dependencies. You might add or remove some later. + +controller.js +------------- + +The controller is where most of the action happens. The controller is complex, but there is one basic thing that needs clarifying. In AngularJS, :code:`$scope` essentially says, "This should be visible inside the associated :ref:`template`." You'll gradually come to understand other aspects of the controller as you work with it. + +Copy and paste the following into :code:`sdk/ecomp-sdk/epsdk-app-os/src/main/webapp/app/fusion/scripts/myapp/myfirstpage/controller.js`: + +.. code-block:: javascript + + appDS2 + .controller('myFirstPageController', function($scope, $routeParams, $location, $interval, $http, $q, $modal, $log, ManifestService, dataService) { + /********************************************** + * FUNCTIONS + **********************************************/ + + $scope.init = function () { + // Set up and initialize a state object. This object will contain information about the state of + // the application as the user interacts with it. + $scope.state = { + // Holds a message string for testing + message: "Hello from myFirstPageController", + } + } + + /********************************************** + * Setup and initialize the app on load + **********************************************/ + + $scope.init(); + + }); // end .controller + +data-service.js +--------------- + +:code:`data-service.js` is the bridge between the Java side of your app and the web side. The dataService makes http requests to :code:`MyAppController.java`. Once a response is received (it will not block, waiting for a response, because you want your app to continue working while waiting), it executes the :code:`then` portion of the code, which simply returns the result back to wherever it was called from. + +We'll see it in action soon. For now copy and paste the following into :code:`sdk/ecomp-sdk/epsdk-app-os/src/main/webapp/app/fusion/scripts/myapp/myfirstpage/data-service.js`: + +.. code-block:: javascript + + appDS2.factory('dataService', function ($http, $q, $log) { + return { + // Service to return chart data + getChartData: function(direction) { + return $http.get("get_chart_data/" + direction + "/").then(function(response) { + if (typeof response.data === 'object' || typeof response.data === 'string') { + return response.data; + } + else { + return $q.reject(response.data); + } + }, function(response) { + return $q.reject(response.data); + }) + } + }; + }); + +route.js +-------- + +:code:`route.js` tells AngularJS how to map specific kinds of incoming requests to specific pages and controllers. AngularJS uses the 'location' hashtag to pass parameters to the client as seen in the commented :code:`when` block example below. + +Copy and paste the following into :code:`sdk/ecomp-sdk/epsdk-app-os/src/main/webapp/app/fusion/scripts/myapp/myfirstpage/route.js`: + +.. code-block:: javascript + + appDS2.config(function($routeProvider) { + $routeProvider + + // Example route that maps a specific URL to another page and + // controller. + // + // This would respond to: + // http://localhost:8080/epsdk-app-os#/date1/2017-08-01/date2/2017-08-07/ + // + //.when('/date1/:date1/date2/:date2/', { + // templateUrl: 'app/fusion/scripts/myapp/myfirstpage/someotherpage.html', + // controller : "anotherController" + //}) + + .otherwise({ + templateUrl: 'app/fusion/scripts/myapp/myfirstpage/template.html', + controller : "myFirstPageController" + }); + }); + +.. _template: + +template.html +------------- + +The AngularJS template holds all the HTML and AngularJS directives that are presented to the user inside the ONAP Portal SDK boilerplate navigation. It is referenced in the :code:`route.js` file. Copy and paste the following into :code:`sdk/ecomp-sdk/epsdk-app-os/src/main/webapp/app/fusion/scripts/myapp/myfirstpage/template.html`: + +.. code-block:: html + + <div id="page-content" class="content" style="padding: 25px;"> + <p>{{state.message}}</p> + </div> + +Now, compile, install, and login. + +Adding your new page to the SDK navigation +------------------------------------------ + +First, click on :code:`Menus` in the :code:`Admin` navigation menu: + +.. image:: img/menus.png + :width: 320px + +Now, click the :code:`Add Menu Item` button at the top of the page: + +.. image:: img/addmenu.png + :width: 200px + +Finally, fill out the form in the following way. + +.. note:: "myfirstpage" is a reference to the name we defined in :ref:`definitions.xml`. + +.. image:: img/newmenuitem.png + :width: 640px + +To reload the navigation, click :code:`Home` in the Portal SDK navigation menu. You should see your new menu item at the top. If all went well, you should see "Hello from myFirstPageController" in the content area to the right of the navigation menu. + |