Part 5: Reading from and Writing to External Systems

Manipulate Work Session data via the Skylight Integration API to learn how to get data to and from your app and integrate it with external systems of record.

Time Required: 30 Minutes

Congratulations on making it this far, you’re in the home stretch! In this series so far you’ve learned how to get your environment set up and have walked through the process of making a simple, but fully functional, checklist-style application that models a fictitious assembly use case. This application models a very simple representation of an assembly process that might take place on the floor of a manufacturing facility at a particular station.

In Part 4, you learned how to set up a work session each time your user assembled some components to form a sub assembly. Now Skylight is not only providing the user with instructions on what to do, but tracking the completion of each run through the assembly process. This is a great start, but is extremely limited in that we need to go back and edit our application data and code any time we want to make any changes. This is exactly the type of thing that most businesses would already have an enterprise system of record in place for, so how do we move data back and forth from external systems? Read on to find out!

Setup

Before we get started, let’s make sure everything is ready for you to work. Please make sure you've completed the following:

Interacting with the Integration API

In this exercise, we’re going to assume that we don’t have direct access to change the system in our company that stores the assembly procedures and that we want to make aware of the status of completion of each process. We’re also going to assume that it has an API that we can access via code. Since this system of record doesn’t actually exist, we’re just going to pretend and hard code some data, but doing so should give you a good idea of how you can integrate Skylight applications with external systems.

Because we can’t modify our fictitious system directly, we’re going to write a little service of our own that acts as a sort of “middle person” between the two systems and keeps the data in sync. You could write this using any language you like because Skylight’s Integration APIs rely on standard technologies like REST over HTTP and MQTT. In addition, Upskill maintains a set of SDKs for popular languages that abstracts these API’s and provides tools to make development even easier. Currently Upskill maintains SDKs for C# (.NET Core) and JavaScript.

In this example, we’re going to use Skylight’s C# SDK and Visual Studio Code, but you’re free to use your IDE of choice!

  1. With generating the credentials out of the way, let’s scaffold our project. We’re going to use the dotnet CLI to set up a new console app project. Run the following commands from a terminal window:

dotnet new console -n quickStartService
cd quickStartService

2. Now that we’ve got our service scaffolded, we'll want to get the Skylight SDK from NuGet. In order to do this, you’ll want to add the Skylight release NuGet feed as a source:

dotnet nuget add source https://pkgs.dev.azure.com/UpskillSDK/skylight-sdk/_packaging/release/nuget/v3/index.json -n skylightSdkRelease

3. Now that we’ve got the appropriate source registered with our local CLI tools, we can pull down the Skylight SDK and register it with our project:

dotnet add package Skylight.Sdk

If all has gone well you should see the following line appear in the quickStartService.csproj in the root of your project folder. *Note that the version number may be different than at the time of the writing of this guide:

<ItemGroup>
<PackageReference Include="Skylight.Sdk" Version="2.0.10" />
</ItemGroup>

Now let’s start getting the code for our service set up. Go back to your project’s root folder and open up the Program.cs file. We’ll start off by importing some libraries. Make sure you’re importing the following libraries:

using System;
using System.IO;
using System.Timers;
using System.Dynamic;
using System.Configuration;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Skylight.Model;
using Skylight.Sdk;

4. We can now create a Managerobject that will let us access functionality from the Skylight Integration API. Modify the body of Program.cs to reflect the following code, replacing the default “Hello World” functionality.

namespace quickStartService
{
class Program : ISessionEventListener
{
static Manager _manager;
static async Task Main(string[] args)
{
_manager = new Manager(new Program());
// If your domain is in the EU update both the API and MQTT with .eu
// both should read .skylight.eu.upskill.io
_manager.SetCredentials("myUsername", "myPassword", "myDomain", "https://api.skylight.upskill.io", "ssl://mqtt.skylight.upskill.io");
_manager.SetVerbosity(Logger.Verbosity.Verbose);
await _manager.Connect();
_manager.KeepAlive();
}
}
}

Replace myUsername, myPassword, and myDomain in the above with your own credentials. While we’re passing these in as hard-coded values in this example for the sake of expediency, you’d never want to build a production service that stored your credentials in this manner. Instead consider using something such as the .NET ConfigurationManager to manage your config and credentials. The code we’ve written to scaffold our service is pretty self explanatory. We’ve specified that our class should implement the ISessionEventListener interface so that we can create methods that handle the various events we’ll receive from Skylight. We’ve also created a new Manager object and set its credentials and log configuration. Afterwards we connect and keep the connection alive indefinitely.

5. You’ll notice that the code above does not compile. That’s because we have not implemented the necessary methods of the ISessionEventListener yet. So let’s do that. Add the following method implementations to your Program class in Program.cs:

async Task ISessionEventListener.OnSessionCreated(SessionEventBase sessionEvent)
{
Console.WriteLine("Session Created");
}
async Task ISessionEventListener.OnSessionPropertiesUpdated(SessionEventBase sessionEvent)
{
Console.WriteLine("Session properties updated: " + sessionEvent.SessionId);
var properties = sessionEvent.Properties;
foreach (KeyValuePair<string, string> kvp in properties)
{
Console.WriteLine("{"+kvp.Key + "," + kvp.Value+"}");
}
}
async Task ISessionEventListener.OnSessionClosed(SessionEventBase sessionEvent)
{
Console.WriteLine("Session closed: " + sessionEvent.SessionId);
}
async Task ISessionEventListener.OnSessionDataUpdated(SessionEventBase sessionEvent)
{
Console.WriteLine("Session data updated");
Console.WriteLine(sessionEvent.ToString());
JObject data = sessionEvent.Data as JObject;
Console.WriteLine(data.ToString());
}
async Task ISessionEventListener.OnSessionEventCreated(SessionEventBase sessionEvent)
{
Console.WriteLine("Session event created");
Console.WriteLine(sessionEvent.ToString());
}
async Task ISessionEventListener.OnSessionUpdated(SessionEventBase sessionEvent)
{
Console.WriteLine("Session Updated Event");
Console.WriteLine(sessionEvent.ToString());
}

Now save your work. You’ve just set up a fully functional scaffold for a service that integrates with Skylight!

6. Since we don’t have a real back-end system we’re integrating with, we’re going to pretend and add some logic in the OnSessionCreated event handler that adds some data to our session. Before we do that, we’re going to have to create a POCO (Plain Old CLR Object) that contains the object classes we’ll use to model our data. If you’ll remember from Part 3, we saved the following data in application data and used it to bind our UX on the client:

{
"processName": "Build Assembly 123",
"steps": [
{ "id": "1", "title": "Prepare Component 1" },
{ "id": "2", "title": "Attach Component 2" },
{ "id": "3", "title": "Attach Component 3" }
]
}

Instead of pulling that information from static application data, let’s store the same data structure (with different data) in each new session that is created. This will simulate our service retrieving data from an external system and providing it to users when they create a new session. First, we’ll have to create some POCO to model our data. Create a new file called AssemblySessionData.cs containing the following classes:

using System.Collections;
using System.Collections.Generic;
namespace quickStartService
{
public class AssemblySessionData
{
public string processName { get; set; }
public List<AssemblyStep> steps { get; set; }
public Dictionary<string, string> stepStatus { get; set; }
}
public class AssemblyStep
{
public string id { get; set; }
public string title { get; set; }
public AssemblyStep(string id, string title)
{
this.id = id;
this.title = title;
}
}
}

Note that we’re breaking a little with traditional C# naming practices for the public members of these classes. This is due to the fact that we used camel case naming in our script and will be using a serializer that will use the names of our properties as-is when serializing/deserializing these objects to/from JSON. You’ll also note that we included stepStatus in our object. If you’ll remember from Part 4, this is the dictionary style object we store in session data to track which pieces of the process have been done. Since we’ll be adapting our application to read all of its data from sessions as opposed to application data, we’ll want to be able to work with this as well.

7. Now let’s write the code that is going to populate new sessions with the appropriate data. Were this example tied to an actual external system of record, this is where you’d put logic that connects to that system and decides what data to extract and/or transform.

async Task ISessionEventListener.OnSessionCreated(SessionEventBase sessionEvent)
{
Console.WriteLine("Session Created - Accessing Procedure");
//Create a new set of data to simulate our process
AssemblySessionData data = new AssemblySessionData();
data.processName = "Process XYZ - From External System";
//Add some steps to the process
data.steps = new List<AssemblyStep>();
data.steps.Add(new AssemblyStep("1", "Prepare Components X and Y"));
data.steps.Add(new AssemblyStep("2", "Prepare Component Z"));
data.steps.Add(new AssemblyStep("3", "Bolt Component Z onto Component Y"));
data.steps.Add(new AssemblyStep("4", "Adjust Ring Adapter of Component X to accept ZY subassembly"));
data.steps.Add(new AssemblyStep("5", "Weld ZY subassembly onto Component X"));
//Since this is a new run-through, each step will be incomplete
data.stepStatus = new Dictionary<string, string>();
for(int i=0; i < 5; i++){
data.stepStatus.Add(i.ToString(), "incomplete");
}
//Update the new session's data
await _manager.PatchSessionData(sessionEvent.ApplicationId, sessionEvent.SessionId, data);
}

As you can see, we’re going to start using the session data to store not only the completion status of the steps in a given procedure, but the details of that procedure as well. This is the place in our service where we’d reach out to the “real” back-end system of record and load up the necessary steps for a given procedure. Since we don’t have one, we’re just going to hard-code it for this example.

8. If we had a real external system to integrate, we’d place logic in the OnSessionDataUpdated event that would inform that system of the status of each step in the process. Since we don’t have a system of record to integrate this example with, we’re going to just leave the Console.WriteLine() calls in place that we added to our scaffolding so that you can observe the changes to the data as you use your application.

async Task ISessionEventListener.OnSessionDataUpdated(SessionEventBase sessionEvent)
{
Console.WriteLine("Session data updated");
Console.WriteLine(sessionEvent.ToString());
JObject data = sessionEvent.Data as JObject;
Console.WriteLine(data.ToString());
}

9. That should do it for our service, but before we start it up, we need to make a couple of small changes to our application in Skylight. One of the great things about using application data the way we did in the first couple of exercises is that it allows us to rapidly prototype our application, get user feedback, etc… before integrating it with other systems. Because we’ve kept our session data model the same, the number of changes we have to make to the application to “glue” them together is minimal. Let’s get started by opening up the Quick Start application from the current tutorials and go to the root view.

10. We’re going to leave the process title bound to application data. For our purposes, this is fine for now. We are, however, going to edit the openStepsList event script associated with that card. Go ahead and open it up and comment the code indicated below (you can also just remove it if you like):

let newSession = await skylight.session.create({ name: skylight.application.data.processName })
await skylight.session.set(newSession.sessionId)
//let newData = {
// "stepStatus": {
// "1": "incomplete",
// "2": "incomplete",
// "3": "incomplete"
// }
//}
//await skylight.session.saveData(newData)
skylight.openView({viewId: "stepsList"})

As you can see, all we’ve done here is remove the bit where we set up the data in the session because we’re doing that through the API now. Click Save to continue.

11. Next, let’s adjust the steps view. Go ahead and navigate to the stepsList view and select the card that you data bound to represent each step in the collection. We’re going to change that card’s context from application.data.steps to session.data.steps because we’re now populating the steps for this process through the API instead of hard-coding them in the application data. When you’re done, the properties panel should look like this:

That’s it! Because our data binding statements are relative to context, that’s the only change we need to make! If you want to avoid any confusion, you could also go back and remove the steps information from the Application Data by removing the steps collection from the JSON (leaving processName alone).

Now you should be able to test your “integrated” application! Go ahead and run your service on your local machine (assuming that machine can assess Skylight in the cloud) with the command dotnet run and watch for console messages as you use the app from the web client or your device! You can also still use session inspector to view the data updates as they’re made.

In this lesson you’ve learned the basics of integrating external systems, but we’ve just scratched the surface. You’ve learned that work sessions are the conduit through which data flows to and from your applications, but the code that interacts with the Skylight Integration API is able to do so much more. You can use the Skylight Integration API to make changes to sessions and their data as well as create or close them! To learn more check out Work Sessions.

Congratulations!

You made it! This series of getting started guides was designed to give you a taste of the major features of Skylight Applications and how you can put them together to create powerful applications with very little code. Like any getting started series, we’ve only scratched the surface of how these tools can be used. From here we hope that you dive deeper into the Skylight developer documentation starting with the Core Concepts and also using the Scripting API Reference.

If you run into any issues that the Troubleshooting section doesn’t cover, you can ask questions on our Developer Community.

Happy building!