 All right, looks like it's time to start. So, hi, I'm David Lyle, I'm the Horizon PTL. Been around for a little bit. We're going to tell you today about some of the changes that we've been putting into Horizon, especially around AngularJS innovating and doing customization work with it. There's been a lot of people involved with the effort, and four of them are on stage here with me. We wanted to bring even more, but they would only allow us to bring five. No, just kidding. Cindy and Richard and Ty and Travis have all helped immensely with this effort. So, let's skip the agenda. So, as we all probably know, Horizon is a Django application with some slight differences in the fact that we don't actually use the database on the server. And so, we generate our model layer by going through the Python service clients to the actual service APIs. So that architecture works pretty well until we start getting into complicated views. And so, I mean, this is actually a standard view in Horizon. This is the instances page, as many of you probably recognize. And as we added complexity to this page, it got to the point where, just to generate that page, we're actually making six API calls to three different services to generate just that one view. And those API calls can take anywhere from the order of milliseconds to several seconds depending on how much data you're returning and the layout of your data center. Obviously, this is pretty inefficient. Now, once you've actually loaded that page and you want to take an action on something in that page, once you've completed that action, you actually have to go do all those API calls again. Because we don't have any server side cache. We don't have a database. So we're actually just going to go pull the data again directly from the server. Obviously, this is really inefficient. And we need to do better. So we've been thinking about how we might possibly do that. So fortunately, there's been some advances in web technology over the last decade that we decided to take advantage of. So JavaScript was the answer that we came across. And we decided on AngularJS as the framework. Why? One of the main reasons is because a lot of the newer JavaScript frameworks require you to write it as a single page app. Angular allows you to do a season to taste model. We can do a little bit or a lot depending on how fast we can move. And so we have a large install base that we need to continue to support while we're going through this transition. And so we picked Angular so that we could do that. And so over the last 18 months, actually, we've been doing that conversion. And one of the things that we picked to focus on initially was launch instance. And the reason is launch instance has proved in our usability testing to be one of the most confusing parts of the UI. And so we thought, what better place to apply the advantages of speed and improve validation than in one of our key pain points? Yeah, so in Kilo, what we did is we actually redid the launch instance wizard. It is available on any horizon installation. You can just toggle it on. So what I've got here is, if I can actually find my way around the screen, is a demo video of what we have. So in this particular install, I'm just going to say I actually have both of them enabled. So you can have them enabled side by side. So it's in beta mode, but let's just talk through it a little bit. So if we first go in and look at the current launch instance wizard, or the one we all know and love, one thing that you'll notice is there's actually very little information available to you. And this affects both new users and advanced users. And if I want to go and see the difference between flavors, I have to pick every single one, see what updates. And it's actually even missing some information that we just don't have access to in this current one. Same for image. We go in. All we're given is a little name, maybe some size associated with how big that image is. But it's pretty honestly worthless for new users. You get to security groups. How do you know which one to pick? I mean, what's telling you? It's just a name. It doesn't tell you what that security group gives you. Maybe we're going to run off to another screen in Horizon to figure out what to do there. Same with networks. Wow, I got a name. That's helpful. So in the new launch instance wizard, we wanted to give you the information right there at your fingertips. And one of the things we really want to do is enable the help content to be right there available for you in real time. So we've been able to whole rich help content system that enables our information engineers to even provide real rich content on a step-by-step basis. Next, we really wanted to make this a much more responsive UI with real time validation that's helpful. You'll see as we up our instance count, you're going to see the quota impact immediately. You exceed it. It instantly gives you feedback, both visually in terms of that red chart. And also, you're going to get a nice text message where I've tried to give you some information to help you make a good decision with what you're actually trying to do. And then down below, it's still kind of the same stuff. You're going to choose your instance, a snapshot volume, whatever, switch over to create new volume. And everything's really dynamic. And Angular gives you this real time binding and validation that's extremely helpful. Now down here, now instead of a simple name, I can actually do sorting and filtering real time and get me some real ways to actually find what I want. And if I go ahead and type in a word like stack, I'm going to see the images that have stack. And here's the really cool part. Now we can expand this out and you're going to see real metadata about it. So taking advantage of the Glance Metadata Definitions Catalog, we actually have the ability to put real, rich information on these images and let end users see that so they can take advantage of that. So if we select this, jump into the next one and look at the flavors step, you're going to see a similar look and feel. So you see this kind of going with a standard feel that you're going to know and love. And you can do the nice sorting. So I want to find things based off the amount of RAM it gives me. I can. If I see an error message, I hover over it and actually gives you a message like, why is this an error? Why can't I select this? And what can I do about that so that we can actually make a different decision? So we're trying to guide users and help them to know why something is available or not available. So in this one, it told us we need at least 512. So we do go ahead and type in 512. You'll see we'll filter down to just those that have 512. And if you look at this, sometimes you're going to have flavors that actually have very similar attributes. And you're like, well, what's really different there? Well, that's where you can actually expand this out now and get more information in real time. So here you go. This one, actually, this is a flavor that apparently has some virtual CPU topology settings associated with it. There's the quota charts again in real time for that particular flavor. And if we expand it out, you're going to see other flavors in the quota associated with it or other things. So this one has trusted compute pulls or some other compute host capabilities on it. So now that's all readily at your fingertips. So if we select that one, go on to Networks. You can always add them through. Again, just to show it's consistent, you're going to get more information available to you. We can do the reordering that will give you different NIC settings. So basically, you're assigning your networks to your NICs. So in this one, I have private on NIC 1 and public on NIC 2. And the Launch Instance button is now enabled. So we could go ahead and launch from here and go. But we'll go ahead the next button. We get into security groups. And like I said before, one of my irritants was, I don't really know what this thing has. So now, well, guess what? I actually know what it means, what the protocols are, the minimum ports, the maximum ports. And you can make these decisions right there without having to go navigate the rest of the UI and find information. So again, we select that. We're going to jump on to our next step. And in key pairs, we can create new key pairs, import new key pairs, or import a key pair, or just select one, you know, some more information. So kind of going with that same standard look and feel. Now, if you happen to have a cloud init script you want to launch, you can pull this in. And an improvement here from the old one is it comes in inline. You see it. You can also edit it. So if you want to bring it in, basically as a template, maybe make some minor tweaks to it, you could. And then choose to launch from there. And also here to point out, there's a few things here that are Nova extensions. I'll make some interesting points about that later. Partitioning, that's something or config drive. Those are things that really are only optionally enabled to you if you have a particular Nova extension enabled. So now that we've taken a look at that, get out of that, and come back to our presentation. Let's talk a little bit about what we did when we were building it. So we didn't just say, oh, we're going to build a big blob of launch instance. We're actually trying to make it a very modular development so that we really built up a whole bunch of new modules that we're intending for reuse to be done in the future. So that when you come in and you're going to be building out your horizon panels due customization in the future, we want it to be very easy for you to be able to just grab these new modules that we're creating and use them as you do your own customizations. So those had made some really good progress in them or still working on them as well. But there's a lot of things that we're building in terms of modularity. So I want to talk a little bit about this. This is something that's really cool with Angular. You actually have what are called directives. And what a directive is, is a way to essentially mark up your HTML with semantic behavior or visual components. So in this case right here, this is really cool. So I say nova extensions here, and I say config drive. This is a directive that is actually going to make a dynamic decision based off if this nova extension is enabled, whether or not to show the content in there. And so it's easy to read. You look at your markup, and you can say, oh, this markup in here is for a particular nova extension. And it's going to take care of ensuring that you only see that if that nova extension actually is enabled in your environment. So it does that by inspecting the service catalog and talking to nova. Now we're also looking at more visual directive. So this is an example of action list, so something that was also built out in this time frame. So you see there up at the top, I've got actions, I've got an edit buttons kind of split, a drop down, and then you can select your change password. You look at the markup, it pretty much matches what you're seeing. So this is a really cool thing. So you can actually, essentially, very easily map from what you're seeing on your screen to what you're seeing in the markup. So we're really quite happy with the direction this stuff is starting to take. Dave was talking a little bit about how you have this problem of, I need to make 13 requests. Maybe they're being done sequentially right now. Well, in Angular, you have this really cool mechanism of promises. And the way a promise works is it essentially says, do this function, and it's asynchronous. So it's going to shoot them off. So in this case, what we're actually happening is we're queuing all these up, saying, do all these functions, glance API, go get the images, nova API, go get availability zones. And we're not waiting for each one. But what it's saying is, when that call returns, so when I say get images where the status is active, then do what you need to do when you've got those images. And so it's got a really simple, nice asynchronous methodology for making multiple simultaneous calls and give us a whole lot less blocking on the behavior you see. And down below, there's either some cool stuff down there, service catalog. This is a service we built on top of our base APIs that we just ask it, hey, if this type is enabled so the network service is basically saying, is neutron enabled, then go ahead and get your networks. And it's a real nice asynchronous mechanism that's been built out. So let's take a look at the general architecture of it. So AngularJS from Horizon, down at the bottom, we still have our services, Nova and Neutron Cinder. You come up and there's a Horizon Django server there. And it provides a purpose. It actually proxies for us some other things. And where we've started is we've actually injected a REST API that's sitting on top of the Django servers and it takes advantage of a bunch of things we still have. And then Angular is the static files that are served out on the client. So actually, this comes out, goes to your browser, and it makes calls into this REST API. So if we drill into that a little bit more, again, essentially have your OpenStack services. They have their Python clients. Horizon itself has client API wrappers and it allows you to deploy Horizon in an environment that you could have like Cinder v1 or Cinder v2 enabled. And these Horizon client wrappers actually make it pretty easy to have a deployment that's kind of mixed version deployment and it takes care of that for us. So initially, we weren't looking to rebuild all of that from scratch. At some point, we probably will consider how we do that, but for now we're able to take advantage of what's already there and built that REST API up on top of it. So then you have your AngularJS REST clients. This is our first layer of abstraction from the actual Angular level. So we have a layer of REST clients that are pure Angular and this is what then we build services on top of. So we say, okay, I have a service, it's called service catalog. Underneath that thing, it's actually making calls to say Keystone API. So we're building up these layers of abstraction so that ultimately our view layers can make use of those and you can build additional views on top of the layers of services that we're building out in the entire environment. So to kind of go into a little bit how you might do that, I'm gonna give it over to Richard Jones here. Thanks. All right, thanks Travis. So I'm gonna give you a little bit of an idea of, let me just see what's going on here, of what this kind of looks like in practice. So what we've got here is it's an Angular view, it's HTML and it's not entirely unlike something we actually have in Horizon now. And what it's doing is looping over a variable called key pairs, which is on the current Angular controller in this page. And it's just displaying the name and the fingerprint from those key pairs in the page. So it expects that key pairs variable to come up from somewhere. So we'll look at where that, how that makes it up into the HTML. At the controller level inside, this is still inside JavaScript. We retrieved the key pairs variable from the Nova API using a call down into the Django REST API that we've implemented. It's a REST API because JavaScript really likes REST APIs and we're quite familiar with REST APIs now in OpenStack. There's a bunch of REST APIs already scattered throughout Horizon, but they're in a pretty ad hoc and inconsistent fashion. So we decided to just go straight out and implement a new consistent API to build the new Angular system on top of. The API service you see here called Nova API has a very similar API pattern to it to the built in Angular HTTP service to keep things nice and consistent for people who are somewhat familiar with Angular already. And one of the things we built into these services is that they have a default error handler. So you can override the error handler inside your code, but by default, if something goes wrong, the user will get a notification saying something relatively pertinent to the thing that was going on. So in this case, we will get an error message up to the user saying that the key pairs fetch failed. The APIs are provided through per OpenStack service Angular services. It's quite unfortunate that they're both called services, but the OpenStack services sit down underneath Horizon effectively, and we are kind of matching those services in the Angular side with things like the Nova API service. And as I said, each of those services provides that default error handler, which again, you can override. We've built a kind of generic API service that knows how to talk to Horizon and handle some of the eccentricities there. And so most of the API services, especially things like just retrieving data, all they have to do is provide the actual URL to POCAT to get the data and the error message to present if there's a problem. Services on the Django side are added using some helpers that we've added to again, provide this kind of consistent REST API on the Django side. So URL registration is done through a common pattern where you create a class, which has the REST methods on it, like get and put and post, and they provide a URL rejects that's matched, and that's passed into the Django URL registration automatically by the URLs.register. And then we've also provided a decorator to handle most of the AJAX handling that you need for each of those methods. So the RESTutils AJAX decorator handles pulling apart the AJAX call. It can enforce, by default, it enforces that the user has to be authenticated. That can be overridden. It also can enforce that data is required in the AJAX call, so you don't have to do that check yourself. And it also handles the response from the method, turning it back into something that our Angular code expects to see. So if we return something like, in this case, a Python data structure, that'll just get turned back into JSON. If we return none, then a HTTP empty status code response is returned back to Angular. We've got some other helpers in there, so you can return a specifically crafted JSON response with a specific status code, and you can also return a created response, which is something that's quite common, which is an empty response, but it has a location header. And again, we've got a helper to help out with that. If, for some reason, you decide you actually need to return an actual HTTP response based on the Django HTTP response, you can return that, and that'll just be passed back through to the Angular code. If there's an error inside the call to the underlying API, that is propagated up to the caller as well. We don't touch that. So exceptions are handled quite sanely. So I'm now going to pass over to Cindy and Tai to talk through an example of actually extending Horizon using this. Okay, does it work? Hello. Thank you, Richard. Okay, so over the next 10 minutes, so we restart that. So over the next 10 minutes, so I'm going to show you how to basically build an Angular table and customize it. So what we start here is we have an empty dashboard, and we're going to basically populate it with a tabular data. So let's get started. So one of the first three things to note is that there are three important files we want to focus on. The first is the five-circle.js, which contain our controller, and data.json, which contain our model, and finally, the index.html, which contain our view. So one of the cool thing Angular does for us is it allows the structure to be, it does a good job at separating the concerns, right? Where all of your control logic would go in the controller and all of your model would exist somewhere in a static file in this scenario, but it could have easily been from some API, and your view is just the HTML. So there's a clear separation there. So what we're really doing in this controller here is we're using the HTTP service to hit some URL to get some data back, and once we have this data, we then bind it to scope.restaurants. So we'll talk a little bit more about scope in a little bit, and we take a look at the data.json, and what we really have here is a list of restaurants where one particular entry is one restaurant, and in the restaurant we have attributes like name, cuisine, rating, and so on. So let's say we want to show this in a table. It's relatively simple to do, so let's go over to our view. The very first thing we note here is that there's something called the ngControllerDirective. What this does is it points this element here to our controller earlier. So whatever we define in the scope, then becomes available in the template. So in this scenario, we can start using restaurants right away. So let's actually go ahead and create a table. First, we'll populate the table with the table header, and we'll include the three columns, name, cuisine, and rating, and we'll again do the same thing for the body. So in the body, one of the things that we're using, another directive actually, is the ngRepeat. What this does is it allows us to then iterate through the list, and then for each item in there, it would basically be R in restaurants. And what we're doing here is we're evaluating the expression and populating that into the table cell. So let's go ahead and take a look at what that looks like. Okay, so we have a very simple looking table, but all of our data is there. It doesn't look very good, so let's change that. We're gonna go back to the code and add in some classes. And for the sake of time, I've created some notes where I can just copy and paste over. Okay, so a little bit about the class. The class here, table stripe is actually from Bootstrap. What it does, it gives you the alternate stripe colors for your table, and the other three classes are actually built into Horizon, and their roles will become a little bit more evident as we add more enhancement features to this table. So let's go ahead and take a look at what that looks like once we added the classes. Should look a lot better. Okay, cool, so it looks a lot better. Now we're actually gonna customize some of the content. So you'll see here that the rating right now is a numerical value. What we wanna do is we wanna replace the numeric value with icons. And in this case, let's say we wanna replace with stars to make it a little bit more yelp-like. So let's do that. So Travis has already sort of talked about NG directive earlier. What we're really doing here is we're going to declare our own custom directive called stars. And what this directive does is it takes in a value, so you give it say three, four, or five. What it does is it then creates a star icon and append it to the parent element. So how do you use that? It's actually really easy to use. So let's go to our index HTML. And what we'll do is we're going to replace the r.rating text with the directive. Stars will pass in the r.rating as the value. And that's all we really have to do. It's really easy to use. So let's go ahead and refresh and see what that looks like. Cool, so there it is. So one of the other things that we haven't done is that there's actually a lot more data in our data.json that we haven't shown, like email and description. We want to show that, but let's assume here that we don't have enough real estate. So let's have some sort of a toggle button where we can click and then we'll have this cool drawer effect that comes out and show us the details. So first step is to add this toggle icon. So we're going to basically introduce an additional column to the left of the table where we'll have this icon available. So we need to add this to both the header with the class expander and the body. Okay, the only difference is that the cell in the body you'll see that there's an actual icon in there, right? It's the Chevron right icon. And we'll also need to add an additional directive to make this work, call hztable and ngcloak. So hztable and hzexpand detail are directives, again, that have been introduced in Horizon recently, that introduce additional functionality to the table. We also have other directives available, like select all and filtering and all of that good stuff. And let's go ahead and take a look at what that looks like that we've added the icon. So when I click on this icon, I'm not expecting anything to happen because that's because we haven't added the detail section yet. So let's actually go back and do that. So again, for the sake of time saving, we're gonna just copy and paste this code over. And we're gonna go ahead and make one additional change. So instead of ngrepe, we're going to use ngrepeat start. What that does is it allows us to say, okay, instead of one row per item, I'm going to actually have two rows per item, where the first row is what we've seen and the second row where the ngrepeat end directives is that is our detail row. So now one item corresponds to two row and the detail will be hidden by default and you can toggle that and you'll see in a little bit. Okay, so when I refresh this page and now when I click on the Chevron icon, I am expecting something cool to happen. Okay, cool. So not that we clicked on it, something cool happens. Email and descriptions available. So we also have a complete example of this. Let me quickly show you what that looks like. Here we also embedded additional pie charts in there. So you can really do anything you want. It's very, very flexible. And one more thing. Let me go ahead and click back on the slide. So our complete demo is actually available on GitHub. There's a link and we also have a real use case of this, real world use of this in the users table patch, which is currently a work in progress. So as a recap for the demo, you can see that Angular helps make the table more flexible by allowing you to replace the values with the stars as well as more interactive because now you can have that detail expansion come out. So a few other key takeaways are that now that we've separated the data and the presentation, we can do a lot of customization, including things like changing colors, headers, moving columns around, adding icons into the table, something that we had a really difficult time doing before. You can do all of this just by altering the HTML template, which is very easy. You don't have to dig through many layers of code. Also, another thing is now Horizon introduces a lot of reusable widgets. You can see some on the page right now. There's the donut pie charts and there's the expansion. There's a lot of widgets that you can use and you can also embed third party widgets easily or create your own and push them upstream, hopefully. Now you can showcase data in any of, you can visualize data in any of the forms, like here you can visualize it with the stars or the pie charts. Angular makes things very easy and it's also very responsive, but don't take my word for it. We'll show you some performance numbers. So here we did a test for the legacy Angular code and the new Angular code. So the old one and then the new patch that is up for review on Garrett. And we used Chrome's network inspector to do the tests and we did 10 manual trials per each of the tasks. And here are some of the numbers that we got. These are the averages of the 10 trials. So in the first row, so Angular performs very well for all tasks except the first row because we still use the base HTML, which is a Django template and that's why it's still relatively slow. All the other actions, so there's no way around that currently but it may change when we rework the horizon navigation. And as you can see in the rest of the table, Angular performs much better. And the reason for that is because most of the tasks, in most of the tasks, we've shifted the rendering logic to the client side and we don't need to make a request to the server to fetch a new page. So one example of that is, oops. Okay. One example of that is that for the user's detail, little panel that we showed, we no longer need a page redirect like we had before where you click the link and you went to a new page to see the details. Now we have the built in detail slide out. So that's why it is zero. Lag. So another thing is now we have instant gratification or not gratification, verification. For things like the verification that Travis showed, if something does not fit like the quota, you will just see the error message right there. You don't have to submit the form and then have Django verified and then return it back. So now we have zero lag for the form validation as well. So here is some information if you wanna follow up with any of this. We have our horizon channel, which is OpenStack-Horizon and we have our horizon meeting every other Wednesday at, yeah, the times and there are IRC nicknames. So looking ahead to Liberty, we spent a lot of time in Kilo working on getting the widget sets and the architecture down as far as how we're gonna use Angular. So in Liberty, we're actually gonna start really taking advantage of that. In Kilo, we do have the launch instance Workflow, it's Marked Experimental. But that's actually in the code that went out with Kilo. But we're gonna move to make that the default in Liberty as well as start applying a lot of that table goodness to the views that are already in Horizon. And the advantages are obvious. We can keep the data down on the client. We have a lot more data to look at from the client side without having to go make continuous API calls. So more and better in Liberty. And we also, we've been working on trying to make this as extensible as possible. More work will be done in Liberty to allow that. We need a few more extension points, but you can actually go and build, as I've demonstrated today, you can actually go and build plug-ins that use Angular and use Angular inside of your custom extensions and leverage the awesome widgets that we've put together in this release. So I think that's what we have for you. Any questions? So I'm very new to OpenStack, but I'm actually pretty familiar with Angular. I'm primarily a Java developer and when I first saw Angular, I fell in love with it because it's sane compared to most JavaScript. You didn't talk anything about your testing infrastructure. I mean, that's one of the big advantages of Angular is that you can actually unit test it. So I'd like to know what you're doing, not only to write the test, but to run them in your build. Sure. So I'll poke at it and then I'll let somebody that actually knows stuff to answer. No, I'm just kidding. So that is actually one of the reasons that we did pick Angular was the testability. I kind of glossed over that in what I was talking, but so we do have spec.js files for all of the modules that we've added. We run Jasmine as our test runner and so we have a Jasmine test suite that runs as part of our integrated build suite. So everything that goes through the gate and horizon is using those Jasmine tests and validating. Are you using Phantom.js or a browser? We are not using Phantom.js right now. We have Selenium that actually is running the Jasmine test. Phantom is a consideration down the road. We're also looking at bringing in Karma so that we can make use of, particularly one of the big things about it is code coverage reports. So we're looking at that as well. There's some blueprints. Actually, there's code patches up. They just haven't, we haven't finished getting them to be yet. Your code coverage into sonar? Oh, no, we haven't done that yet. Well, if there's no more questions, I guess we can call it good and thank you all very much for attending.