Queries

Download this manual as a PDF file

This section describes how to form a query in GraphQL to search for and return data from fields in SL1. It describes how to form basic query syntax and use connections, variables, fragments, directives, search filters, and pagination options in your queries. Each section includes one or more examples to further illustrate each topic.

GraphQL is a complex language, and the information in this section is not an exhaustive list of every possible option at your disposal when writing and executing a query. For additional information about any of the subjects in this section, or for information not covered in this section, see the GraphQL documentation.

What Are Queries?

Queries are GraphQL operations that search for and return data from fields in your schema. A single query can search for and return data for a single field or multiple fields.

When building a query, it should match the shape of the object types in your schema. GraphQL returns the results in the same shape as your query.

To see a full list of queries that are available for SL1, see the schema in the GraphiQL browser.

Basic Query Syntax

The most basic syntax of a query involves the following elements:

  • Operation type. For queries, this is query. If only one query is present in your GraphQL browser, specifying the query operation type at the beginning of your query is optional; if you do not specify an operation type, GraphQL assumes that you are performing a query by default.
  • Operation name. A name that you define for the operation. An operation name is required if you are including variables in your query. While operation names are not always required in other circumstances, they are still helpful to have for logging and debugging purposes.
  • Object. The object you are querying, as defined in the schema. This is typically the object's name, written in camel case.
  • Argument(s). The data that you want to pass to the object in the query. These are defined in the following structure: (fieldName: "value"). The schema lists the argument field name and return type pairs that are available for each object. Some objects can have multiple arguments, in which case the argument structure is (fieldName: "value", fieldName: "value"), with any additional arguments separated by additional commas. When including multiple arguments, the arguments can be provided in any order and GraphQL will consider them to be semantically identical.
  • Return field(s). The fields that you want GraphQL to return values for when you execute the query.

If you are unsure of which arguments or return fields are required for a query object, type query {objectName} in the GraphiQL user interface, replacing objectName with the name of the query object, and then click the Execute Query (Play) button. GraphQL will automatically add any required return fields to the query, while the results pane will display an error that lists any required arguments.

If you query multiple objects, GraphQL executes the queries on all of those objects at the same time, in parallel.

Example: Querying a Single Device

Here is an example of a basic query. In this example, we are searching for information about a specific device in our SL1 system:

query getDevice {
  device(id: 3) {
    ip
    deviceClass {
      class
      description
    }
  }
}

In this example:

  • query is the operation type.
  • getDevice is the operation name that we defined for this specific operation.
  • device is the object. We are asking GraphQL to search for and return data about a device in our SL1 system.
  • id:3 is the argument for device, with id being the field and 3 being the value in the field: value pair that makes up an argument. We are asking GraphQL to search for and return data about the device with device ID "3".
  • ip and deviceClass are the return fields. Additionally, deviceClass has two nested fields, class and description. We are asking GraphQL to return the device IP address as well as the class name and description of the device's device class.

When we execute this query, GraphQL returns exactly the data we asked for, and in the exact structure we specified:

{
  "data": {
    "device": {
      "ip": "10.100.100.26",
      "deviceClass": {
        "class": "ScienceLogic, Inc.",
        "description": "EM7 Data Collector"
      }
    }
  }
}

Example: A Simple Query without the Operation Type "query"

If only one query is present in your GraphQL browser, you do not need to specify the query operation type at the beginning of your query. Because a query is GraphQL's default operation type, GraphQL assumes that you are performing a query if you do not specify otherwise.

Here is the same query as the previous example, but without the query operation type specified:

{
  device(id: 3) {
    ip
    deviceClass {
      class
      description
    }
  }
}

When we execute this query, GraphQL returns the exact same data as was returned in the previous example, and in the same structure.

Connections, Edges, and Nodes

The SL1 GraphQL schema uses connections, edges, and nodes to connect defined elements and to handle pagination.

  • A connection is a type within the GraphQL schema that is used to connect other defined elements in the schema. A connection consists of a group of related edges. Any type with a name that ends in "Connection" is a connection type.
  • An edge is a type that connects two nodes, representing some sort of relationship between them. An edge also has a cursor in addition to the underlying node.
  • A node is an individual object type that is defined in the schema, consisting of one or more fields.

For example, in the SL1 GraphQL schema:

  • The query object devices includes a connection type, DeviceConnection.
  • The DeviceConnection type includes edges.
  • Those edges link the devices node to the Device node.
  • The Device node includes multiple fields that are used to define an individual device in SL1.

When you execute a query, you define the shape in which you want GraphQL to return data, but that shape must ultimately conform to the shape of data as it has been defined in the schema. That means you will often need to include edges and nodes in your query, as they are part of the structure within the SL1 GraphQL schema. You do not need to include the connection type, as it is part of the query object definition.

Example: A Query with Edges and Nodes

Here is an example of a query that includes edges and nodes, because those things are defined in the schema as being part of the data shape:

query defineDevices{
  devices(first: 5) {
    edges {
      node {
        id
        ip
        deviceClass {
          class
          description
        }
      }
    }
  }
}

When we execute this query, GraphQL returns the requested data for each node:

{
  "data": {
    "devices": {
      "edges": [
        {
          "node": {
            "id": "3",
            "ip": "10.100.100.26",
            "deviceClass": {
              "class": "ScienceLogic, Inc.",
              "description": "EM7 Data Collector"
            }
          }
        },
        {
          "node": {
            "id": "27",
            "ip": "10.2.5.72",
            "deviceClass": {
              "class": "VMWare",
              "description": "vCenter Server Appliance"
            }
          }
        },
        {...3 more nodes 
        }
      ]
    }
  }
}

Variables

Variables are dynamic values that can be used to replace arguments in your query, enabling you to reuse the query for multiple objects simply by changing the variable value.

When using variables, keep the following in mind:

  • Variable names are always preceded by a dollar sign ($). For example: $variableName
  • Before you can use a variable in a query, you must first declare the variable acceptable to use in the query.
  • Place the declared variable in parentheses immediately after the operation name.

  • Use the format $variableName: scalarType, where $variableName represents the name of the variable and scalarType represents the acceptable scalar type for that variable.

  • Variable scalar types must match the type of the arguments that they will replace. For example, if you are replacing a field that has a scalar type of "String", then your variable must also use the "String" scalar type.

  • If you declare a variable after the operation name, you must then use it in your query. You cannot declare a variable and then not use it in the query.

  • Example: query findUserAccount($userID: ID)

  • Optionally, when declaring a variable, you can assign the variable a default value.

    • Place the default value immediately after the scalar type in the declaration, preceded by an equal sign (=).

    • Use the format $variableName: scalarType = defaultValue, where $variableName represents the name of the variable, scalarType represents the acceptable scalar type for that variable, and defaultValue represents the default value for that variable.

    • Any variables that you do not define in the Query Variables pane will use the default value.

    • Any variable values that you define in the Query Variables pane will override the default value.

    • Example: query findUserAccount($userID: ID = 15)

  • You must then insert the declared variable into your query.

    • Place the variable in parentheses immediately after the query object name, just like you would a regular argument.

    • Use the format fieldName: $variableName, where fieldName represents the argument field name and $variableName represents the variable name.

    • Example: account(id: $userID)

  • Finally, you must define the variable.

    • In the Query Variables pane at the bottom of the GraphiQL browser, define the variable value as a JSON object.

    • Use the format "variableName": "value", where "variableName" represents the variable name and "value" represents the value that you want to use in place of the variable in the query.

    • If you included a default value in your variable declaration, and the variable is using the default value, you do not need to define the variable in the Query Variables pane.

    • Example: { "userID": "23" }

Example: Querying Basic Device Information Using a Variable

Here is an example of a query that uses a variable. In this example, we are asking GraphQL to return some basic information about a device, using a variable $deviceID that will be defined at the time we run the query to replace the value in the id argument:

query deviceBasicInfo($deviceID: ID!) {
  device(id: $deviceID) {
    id
    name
    ip
    organization {
      id
    }
    deviceClass {
      class
      description
    }
    state
    active {
      userDisabled
      unavailable
      maintenance
      systemDisabled
      userInitiatedMaintenance
    }
  }
}

We must then define this variable. To do so, we would type its value into the Query Variables pane in the GraphiQL browser in the following format:

{ 
  "deviceID": "27" 
}

Fragments

Fragments are reusable units that you can include in multiple queries or mutations. Each fragment consists of a group of fields that are all associated with the same type.

You might use fragments if you want to reuse the same set of fields for multiple objects. Rather than retyping the same set of fields throughout your query, you can define those fields as a fragment and then just insert that fragment in your query or mutation as needed.

Fragments are defined in the following format, where fragmentName represents the name you are giving the fragment, Type is the type to which the fragment belongs, and fieldNames are the names of one or more fields that belong to that type:

fragment fragmentName on Type {

fieldNames

}

 

After you have defined your fragment, you can then use it in any query or mutation in the same location where the fields contained within the fragment would normally go, using the format ...fragmentName.

When using fragments in your queries and mutations, keep the following in mind:

  • If you use multiple named fragments in the same document, each fragment's name must be unique. Inline fragments do not have this requirement.
  • All fragments, whether named or inline, can be declared only on objects, unions, and interfaces. (For more information about unions and interfaces, see the GraphQL documentation.)
  • If you define a fragment, it must be used in your query or mutation.
  • You can use variables inside fragments. When you do so, add the variable to the appropriate field in the fragment definition. Then, rather than defining the variable in the Query Variables pane, you can define it when you declare it at the beginning of the query.

Example: Creating a Fragment for Account Fields

This query example demonstrates how to create and use a fragment within a query. In this example, we are creating a fragment called accountInfo that is associated with the Account type. The fragment includes the fields id and user, plus the nested fields firstName, lastName, and email under the field contact:

fragment accountInfo on Account {
  id
  user
  contact {
    firstName
    lastName
    email
  }
}

query basicAccountInfo {
  accounts(search: {organization: {has: {company: {eq: "System"}}}}) {
    edges {
      node {
        ...accountInfo
      }
    }
  }
}

Inline Fragments

If you are querying a field that returns an interface or union type, you must use an inline fragment for GraphQL to return data from one of those types.

For more information about unions and interfaces, see the GraphQL documentation.

When you use an inline fragment, you do not define the fragment separately from the query or mutation in which it is being used. Instead, you define the fragment inline within a selection set.

Within a query or mutation, inline fragments are written in the following format, where Type is the type to which the fragment fields belong, and fieldNames are the names of one or more fields that belong to that type:

... on Type {

fieldNames

}

Example: Using an Inline Fragment

The following example uses an inline fragment. In the SL1 GraphQL API, the location field has the custom Address type as a return type. In turn, the Address type has the possible custom USAddress type, which consists of additional return fields. Due to this structure, if we want to query those fields, we will need to nest the fields under the location field using an inline fragment when we form the query, specifying that the location fields that we are including in the query belong to the USAddress type within the schema:

query additionalAccountInfo {
  account(id: 25) {
    location {
      ... on USAddress {
        address
        city
        state
        zip
      }
    }
  }
}

Directives

Directives are keywords that you can use to make GraphQL perform custom logic in your queries. They can be attached to a field or a fragment that you are including in your query, and can affect the query execution and the results that GraphQL fetches.

This is useful for situation where you otherwise would need to manually add or remove fields in your query based on specific circumstances.

Each directive can appear only after the field or fragment that it decorates. They are preceded by the "@" character and can include their own arguments. They are often used in conjunction with variables to create queries with more complex dynamic logic.

GraphQL includes the following default operational directives:

Directive Argument Description
@skip (if: Boolean!) If true, the field or fragment the directive decorates is skipped and not resolved by GraphQL.
@include (if: Boolean!) If true, the field or argument the directive decorates is resolved and included in the operation results.

Example: Using a Directive with a Variable

The following example illustrates a query that uses a directive with a variable. In this example, we have declared a variable, $withIcon, and given it a default value of Boolean = true. We have then included the directive @include(if: $withIcon) on the icon field. Because we have defined the default value of the $withIcon variable as true, we are telling GraphQL to include the icon field in the query results if the device has an icon; otherwise, if the device does not have an icon, GraphQL will still return data about that device but it will not include data about the icon in the results for that device, since it does not have one:

query devices($withIcon: Boolean = true) {
  devices(first: 30) {
    edges {
      node {
        id
        name
        ip
        icon @include(if: $withIcon) {
          id
        }
      }
    }
  }
}

As with any variable, you can override the default value by defining a different value for the variable in the Query Variables pane.

Search Filters

GraphQL lets you add search expressions that filter the data returned in queries based on fields and parameters that you specify. So, for instance, let's say that you want GraphQL to query only those devices that belong to a specific device class. You could use a filter to limit the query to just the devices within that specific device class.

Filters are passed through fields as arguments. The basic syntax of a filter argument is:

(search: {<field>: {<operator>: <value>}})

 

Depending on your schema, your search argument could be longer, with additional fields and operators that ultimately precede the filter value.

If your search argument includes a list, it might also include an array, which is wrapped in square brackets, [ and ]. For example:

(search: {<field>: {<operator>: [<value 1>, <value 2>, <value 3>]}})

Filter Operators

The fields in your search filter have specific allowable operators, which are defined for each field in the schema.

The following table describes the operators that you can use in GraphQL filters.

Operator Function
and returns data only where all parts separated by and are true
or returns data if any parts separated by or are true
not returns data only where the parts following not are not true

eq

equals

neq

does not equal

gt

greater than

lt

less than

gte

greater than or equal to

lte

less than or equal to
beginsWith string begins with
endsWith string ends with
contains string contains
doesNotBeginWith string does not begin with
doesNotEndWith string does not end with
doesNotContain string does not contain
in value is included in an explicit set
notIn value is not included in an explicit set
from value is included in a calculated set
has non-null calculated set intersection

isNull

value is null
isNotNull value is not null
isTrue value is true
isFalse value is false

Example: Querying Devices from the Same Device Class

Here is an example of a query that uses a search filter. In this example, we are asking GraphQL to return the device IDs of all the devices in our SL1 system that have the device class "ScienceLogic, Inc.":

query getSL1Appliances {
  devices(search: {deviceClass: {has: {class: {eq: "ScienceLogic, Inc."}}}}) {
    edges {
      node {
        id
      }
    }
  }
}

Example: Querying a List of Specific Devices

Here is an example of a query that uses a search filter that includes a list of specific devices. In this example, we are asking GraphQL to return the device IDs, IP addresses, and device class names and descriptions for three specific devices in SL1—the devices with device IDs 3, 27, and 2132:

query specificDevices {
  devices(search: {id: {in: [3, 27, 2132]}}) {
    edges {
      node {
        id
        ip
        deviceClass {
          class
          description
        }
      }
    }
  }
}

Query Pagination

When you write and execute a GraphQL query, GraphQL might return a large number of data results matching that query. Obviously, it would be difficult to wade through an extremely long list of results. Pagination can help make the list of results more manageable and readable.

The SL1 GraphQL API uses cursor pagination, which uses several possible arguments to set a unique identifier—a cursor—that maps to a specific record in your data set. GraphQL can then use that cursor as a bookmark to determine where pagination should begin or end. The identifier type varies based on the data field and its scalar type; it might be an integer or a string. You can also use variables to determine the cursor position.

You can use the following arguments in cursor pagination:

  • The after argument indicates the unique identifier of the record you want to establish as the cursor, after which you want GraphQL to start returning results. Here are a few examples:
    • The argument (after: 5) would return results starting with the sixth record in the list that matched your query.
    • The argument (after: "example_cursor") would return results that matched your query starting with the record after the one that included the field determined by the cursor you enter.
  • The first argument indicates the number of records to return from the record established as the cursor. Here are a few examples:
    •  The argument (first: 10) would return the first 10 records that matched your query.
    • The argument (first: 25, after: "example_cursor") would return results for the first 25 records that matched your query after the one that included the field determined by the cursor you enter.
  • The before argument indicates the unique identifier of the record you want to establish as the cursor, before which you want GraphQL to start returning results. Here are a few examples:
    • The argument (before: 15) would return the first 14 records that matched your query.
    • The argument (before: "example_cursor") would return results that matched your query up to but not including the record that included the field determined by the cursor you enter and working backwards towards the beginning of the data set.
  • The last argument indicates the number of records to return prior to the cursor.
    • The argument (last: 10) would return the last 10 records that matched your query.
    • The argument (last: 30, before: "example_cursor") would return results for the last 30 records that matched your query before the one that included the field determined by the cursor you enter..

There is one additional important piece to query pagination: The pageInfo field. The pageInfo field typically goes at the end of your query, below the data fields that you want to query, and can display information about the total number of results that match your query parameters, as well as information about whether the page of results that displays when you execute the query has additional pages of results before or after it.

You can use one or more of the following nested fields under the pageInfo field:

  • The hasPreviousPage field indicates if there are additional results prior to the page of fetched data results. It will display true in the data results if there are additional pages; otherwise, it will display false.
  • The hasNextPage field indicates if there are additional results after the page of fetched data results. It will display true in the data results if there are additional pages; otherwise, it will display false.
  • The matchCount field indicates the total number of records that can be fetched for your query. This is helpful, as it lets you know how many batches you will need to fetch.

When paging backwards over a data set, the hasPreviousPage field nested under pageInfo is used in conjunction with the last and before arguments in other parts of your query.

When paging forward over a data set, the hasNextPage field nested under pageInfo is used in conjunction with the first and after arguments.

The first, after, and hasNextPage arguments should be used individually or together in your query, as should the last, before, and hasPreviousPage arguments. The first and before arguments and last and after arguments should not be used together.

Example: Querying the First Five Devices Listed in SL1

Here is an example of a query that uses a simple pagination method. In this example, we are searching for information about the first five devices listed in our SL1 system:

query firstFiveDevices {
  devices(first: 5) {
    edges {
      node {
        id
        name
      }
    }
  }
}

Example: Determining the Total Number of Query Results

Here is an example of a query that uses the pageInfo field and its nested field matchCount to determine the total number of results for this query:

query howManyPages {
  devices(first: 3) {
    edges {
      node {
        id
      }
    }
    pageInfo {
      hasPreviousPage
      hasNextPage
      matchCount
    }
  }
}

In the query data results that display when we execute the query, we can see from the matchCount field value that there are 25 devices that match our query:

{
  "data": {
    "devices": {
      "edges": [
        {
          "node": {
            "id": "3"
          }
        },
        {
          "node": {
            "id": "27"
          }
        },
        {
          "node": {
            "id": "2132"
          }
        }
      ],
      "pageInfo": {
        "hasPreviousPage": false,
        "hasNextPage": true,
        "matchCount": 25
      }
    }
  }
}

Example: Using Cursor-based Query Pagination

Here is an example of a query that uses a more complex cursor-based pagination method. In this example, we are searching for information about the first 10 devices that appear in the data set after a specific device, which we will define with a variable.

First, we must determine the cursor value for the record that we want to establish as the cursor. In this example, we want to use the 15th device as the cursor, so we would first do the following in GraphQL:

query getTenDevices {
  devices(first: 15, after:"") {
    edges {
      node {
        id
        ip
        name
        state
      }
      cursor
    }
    pageInfo {
      hasNextPage
    }
  }
}

That query returns the following data results:

{
  "data": {
    "devices": {
      "edges": [
        {
          ...first 14 records
        },
        {
          "node": {
            "id": "27",
            "ip": "10.2.5.72",
            "name": "10.2.5.72",
            "state": "0"
          },
          "cursor": "OpcmVjdGlvbiI6ImFzYyJ9XX0="
        }
      ],
      "pageInfo": {
        "hasNextPage": true
      }
    }
  }
}

In these results, we see that the cursor value for the 15th device is "OpcmVjdGlvbiI6ImFzYyJ9XX0=". We now need to use that cursor value in combination with a variable to get the first 10 device records after that 15th device. To do this, we will create a new query that uses a variable $after as the value of the argument after:

query getTenDevices($after: String!) {
  devices(first: 10, after: $after) {
    edges {
      node {
        id
        ip
        name
        state
      }
      cursor
    }
    pageInfo {
      hasNextPage
    }
  }
}

Note that, in the query above, the hasNextPage field nested under pageInfo is used in conjunction with the first and after arguments passed through the devices field. When paging forward through a data set using cursor-based pagination, the hasNextPage field is always used in conjunction with the first and after arguments; when paging backwards through a data set, the hasPreviousPage field nested under pageInfo is always used in conjunction with the last and before arguments.

Finally, we must define the variable $after with the cursor value that we previously determined. To do so, we would type its value into the Query Variables pane in the GraphiQL browser in the following format:

{ 
  "after": "OpcmVjdGlvbiI6ImFzYyJ9XX0=" 
}

When we execute the query, GraphQL returns the requested data about the first 10 records after the cursor.

Querying the ScienceLogic GraphQL API from an External Application

After you have determined the queries you want to execute using the GraphiQL interface, you can execute those queries from an external application by performing an HTTPS request to the GraphQL URI for your SL1 system. To execute a GraphQL query using an HTTPS request:

  • Use the POST method.
  • Set the content-type header to "application/json".
  • Specify the username and password of an appropriate user account.
  • In the request content, send JSON in the following structure:
  • {

    "query":"[GraphQL query]"

    }

For example, to execute the original example query, you would POST the following JSON:

{

"query":"query devices { devices { edges { node { id name} } } }"

}