Graphem
🚀 Concept
Graphem is a plugin that allows viewing telemetry data in NASA Open MCT directly from a GraphQL server.
-
NASA Open MCT is a next-generation mission operations data visualization framework. Web-based, for desktop and mobile.
-
GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data.
🪐 Installation
You can install Graphem from your favorite package manager:
# Yarn
yarn add graphem
# NPM
npm install graphem
Once installed in your project you can integrate it in the <head>
tag using:
<script src="node_modules/graphem/dist/index.js"></script>
Before connecting the GraphQL server you will need a JSON
dictionary file. This file contains the structure of the folder, how each subscription is managed, and the naming of the units. This is file is usually stored in the client.
Here is a basic example with the prop_happiness
object:
/* dictionary.json */
{
"name": "Name of the mission",
"key": "your_key",
"measurements": [
{
"name": "Happiness",
"key": "prop_happiness",
"values": [
{
"key": "value",
"name": "Value",
"units": "kilograms",
"format": "float",
"min": 0,
"max": 100,
"hints": {
"range": 1
}
},
{
"key": "utc",
"source": "timestamp",
"name": "Timestamp",
"format": "utc",
"hints": {
"domain": 1
}
}
]
}
]
}
...
openmct.install(Graphem({
namespace: "rocket.taxonomy", // Custom namespace
key: "orion", // Custom Key
dictionaryPath: "/dictionary.json", // Path of dictionary
telemetryName: "rocket.telemetry", // Name of telemetry
subscriptionName: "formatted", // Name of the <GraphQL> subscription for historical telemetry
urn: "localhost:4000/graphql" // Source URN (Uniform Resource Name)
}));
openmct.start();
🛰 Create a GraphQL server
In order to use Graphem correctly you can use the server template that we provide.
Template GraphQL server from Graphem
This server has a query available to obtain historical telemetry values, and a subscription to obtain real-time telemetry values.
It has minimal setup in TypeScript, and comes with Nodemon ideal for development on top of it.
💻 Development
✨ Structure
Graphem's source code is written in TypeScript. This is a file with a default export function called Graphem. This Graphem function returns a function install
.
export default function Graphem(configuration: IGraphemConfiguration) {
...
return function install(openmct: IOpenMCT) {
...
I developed this following the structure recommended by the NASA Open MCT documentation for plugins. So the install function is the function that is executed when importing and using the plugin in a client with Open MCT.
In both cases we use an interface to check the passed parameters. IGraphemConfiguration
is defined at installation time and contains information about the connection between GraphQL and Open MCT.
IOpenMCT
instead is an interface to ensure intellisense over Open MCT functions. Since Open MCT is not written in TypeScript.
⚙️ Configuration Object
The configuration object of Graphem is a parameter that contains relevant information to start with the plugin. This object is defined by the IGraphemConfiguration
interface. Some fields are necessary, and others are optional.
interface IGraphemConfiguration {
namespace: string;
key: string;
dictionaryPath: string;
telemetryName: string;
subscriptionName: string;
urn: string;
telemetryType?: {
name: string;
description: string;
cssClass?: string;
};
}
🎟 GraphQL Client Connection
When installing Graphem we create a GraphQL client whose main purpose will be to provide real-time information about the data transferred.
As the real-time transfer is established on WebSocket. A computer communications protocol that provides full-duplex communication channels.
When establishing the connection we require the server address, this address can vary a lot in each use case, so it is established through the URN
variable in the Graphem configuration when installed.
Also the createClient
function comes from graphql-ws
library.
const client = createClient({
webSocketImpl: WebSocket,
url: `ws://${configuration.urn}`,
});
🌳 Root creation
Before connection and population of data, Graphem will set a new object root. This process will expose a telemetry folder as a hierarchy of telemetry-providing domain objects.
const objectRoot = {
namespace: configuration.namespace,
key: configuration.key,
};
openmct.objects.addRoot(objectRoot);
🧭 Object Provider
The object provider will build Domain Objects. The structure of the Domain Objects comes from the dictionary.
const objectProvider: ObjectProvider = {
get: async (identifier: DomainObjectIdentifier) => {
const dictionaryResponse = await fetch(configuration.dictionaryPath);
const dictionary = await dictionaryResponse.json();
if (identifier.key === configuration.key) {
return {
identifier,
name: dictionary.name,
type: OBJECT_TYPE.FOLDER,
location: "ROOT",
};
} else {
const measurement = dictionary.measurements.find(
(m: Measurement) => m.key === identifier.key
);
return {
identifier,
name: measurement.name,
type: configuration.telemetryName,
telemetry: {
values: measurement.values,
},
location: `${configuration.namespace}:${configuration.key}`,
};
}
},
};
openmct.objects.addProvider(configuration.namespace, objectProvider);
🗂 Composition Provider
This is a crucial part of Graphem. While Open MCT provides a solution for a composition provider, in Graphem we define a custom composition provider.
Every provider has appliesTo
and load
methods.
The appliesTo
method will filter domain objects by the namespace specified in the configuration parameter, and by the type of FOLDER
.
After that we load (from the load
method) the mesaurements objects from the JSON dictionary. This process starts requesting the object from a dictionaryPath
(provided by the NASA developer) and then destructuring each object returning the key
property with the namespace
as a whole object in an array.
const compositionProvider = {
appliesTo: (domainObject: DomainObject) => {
return (
domainObject.identifier.namespace === configuration.namespace &&
domainObject.type === OBJECT_TYPE.FOLDER
);
},
load: async () => {
const dictionaryResponse = await fetch(configuration.dictionaryPath);
const dictionary = (await dictionaryResponse.json()) as IDictionary;
return dictionary.measurements.map((m: Measurement) => {
return {
namespace: configuration.namespace,
key: m.key,
};
});
},
};
openmct.composition.addProvider(compositionProvider);
🏛️ History
The development of Graphem began by generating a prototype of how to build a plugin that obtains basic GraphQL queries. Although I was able to use the Apollo client, I preferred to use the fetch API to get more lightness in the plugin.
The structure of Graphem will be the following. It is made up of a part to integrate domain objects with object provider and composition provider. Then require the historical data, to display it on the screen, and finally require the real-time data continuously to link them on the same screen.
I was inspired by NASA Spacecraft tutorial to develop these parts.
Currently NASA Open MCT does not support TypeScript (see these issues), but for a better development experience this plugin was built on it.
For better integration with the Graphem package it uses RollUp, a module bundler, in the same style as Open MCT YAMCS. Thanks to this same configuration, it will export a file in UMD (Universal Module Definition) format.
🤲 Contributing
Do you would like to contribute? Do you want to be the author of a new feature? Awesome! please fork the repository and make changes as you like. Pull requests are warmly welcome.
📃 License
Distributed under the Apache 2.0 License.
See LICENSE
for more information.