Example Concepts and Workflows

User Management

Overview 

In the following example we will step through the API calls used to view all users, create a new user, and verify their creation.

 

Components 

- Signals REST APIs

 

API Calls used

- GET /users
- POST /users/

 

Steps

First we will look at all our users using the GET /users endpoint.
 

API Call

GET /users

Example API URL

https://snb.example.com/api/rest/v1.0/users?enabled=true&page%5Boffset%5D=0&page%5Blimit%5D=20

API Response Body

{
  "links": {
    "self": "https://snb.example.com/api/rest/v1.0/users?enabled=true&page[offset]=0&page[limit]=20",
    "first": "https://snb.example.com/api/rest/v1.0/users?enabled=true&page[offset]=0&page[limit]=20"
  },
  "data": [
    {
      "type": "user",
      "id": "100",
      "links": {
        "self": "https://snb.example.com/api/rest/v1.0/users/100"
      },
      "attributes": {
        "lastLoginAt": "2023-07-26T14:54:47.714Z",
        "createdAt": "2023-05-16T23:11:10.236Z",
        "userId": "100",
        "userName": "ross.geller@centralperk.com",
        "email": "ross.geller@centralperk.com",
        "firstName": "Ross",
        "lastName": "geller",
        "country": "USA",
        "organization": "centralperk",
        "isEnabled": true
      },
      "relationships": {
        "roles": {
          "data": [
            {
              "type": "role",
              "id": "1",
              "meta": {
                "links": {
                  "self": "https://snb.example.com/api/rest/v1.0/roles/1"
                }
              }
            },
            {
              "type": "role",
              "id": "4",
              "meta": {
                "links": {
                  "self": "https://snb.example.com/api/rest/v1.0/roles/4"
                }
              }
            },
            {
              "type": "role",
              "id": "2",
              "meta": {
                "links": {
                  "self": "https://snb.example.com/api/rest/v1.0/roles/2"
                }
              }
            },
            {
              "type": "role",
              "id": "3",
              "meta": {
                "links": {
                  "self": "https://snb.example.com/api/rest/v1.0/roles/3"
                }
              }
            }
          ]
        },
        "systemGroups": {
          "links": {
            "self": "https://snb.example.com/api/rest/v1.0/users/100/systemGroups"
          }
        }
      }
    }
  ],
  "included": [
    {
      "type": "role",
      "id": "3",
      "links": {
        "self": "https://snb.example.com/api/rest/v1.0/roles/3"
      },
      "attributes": {
        "id": "3",
        "name": "Standard User"
      }
    },
    {
      "type": "role",
      "id": "4",
      "links": {
        "self": "https://snb.example.com/api/rest/v1.0/roles/4"
      },
      "attributes": {
        "id": "4",
        "name": "Inventory Admin"
      }
    },
    {
      "type": "role",
      "id": "1",
      "links": {
        "self": "https://snb.example.com/api/rest/v1.0/roles/1"
      },
      "attributes": {
        "id": "1",
        "name": "System Admin"
      }
    },
    {
      "type": "role",
      "id": "2",
      "links": {
        "self": "https://snb.example.com/api/rest/v1.0/roles/2"
      },
      "attributes": {
        "id": "2",
        "name": "Config Admin"
      }
    }
  ]
}


In our response we see all users. The data object contains information about each user. In this case there is only one user, Ross Geller. Each user returned has their unique user attributes as an object as well as their related resources. In the case of a user these related resources are the roles associated with the user and their system groups.

To add a standard user we use the POST /users/ endpoint. We specify the required details as the body of our API request. We will use the information from our previous request to determine the id and name of the role for a Standard User. Alternatively we could have just requested a full list of Roles via GET /roles.

API Call

POST /users/
 

Example of API URL

https://snb.example.com/api/rest/v1.0/users

 

API Request Body

{
  "data": {
    "attributes": {
      "country": "USA",
      "emailAddress": "rachel.green@centralperk.com",
      "firstName": "Rachel",
      "lastName": "Green",
      "organization": "centralperk",
      "roles": [
        {
          "id": "3",
          "name": "Standard User"
        }
      ]
    }
  }
}

API Response Body

{
  "links": {
    "self": "https://snb.example.com/api/rest/v1.0/users/101"
  },
  "data": {
    "type": "user",
    "id": "101",
    "links": {
      "self": "https://snb.example.com/api/rest/v1.0/users/101"
    },
    "attributes": {
      "createdAt": "2023-07-26T15:17:24.693Z",
      "userId": "101",
      "userName": "rachel.green@centralperk.com",
      "email": "rachel.green@centralperk.com",
      "firstName": "Rachel",
      "lastName": "Green",
      "country": "USA",
      "organization": "centralperk",
      "isEnabled": true
    },
    "relationships": {
      "roles": {
        "data": [
          {
            "type": "role",
            "id": "3",
            "meta": {
              "links": {
                "self": "https://snb.example.com/api/rest/v1.0/roles/3"
              }
            }
          }
        ]
      },
      "systemGroups": {
        "links": {
          "self": "https://snb.example.com/api/rest/v1.0/users/101/systemGroups"
        }
      }
    }
  },
  "included": [
    {
      "type": "role",
      "id": "3",
      "links": {
        "self": "https://snb.example.com/api/rest/v1.0/roles/3"
      },
      "attributes": {
        "id": "3",
        "name": "Standard User"
      }
    }
  ]
}

In the success response we see the details of our new user Rachel Green. Since she only has a single role our only included information is the Standard User role.

We can verify this further with another look at GET /users using the same API request as earlier. Our response now contains two users, Ross and Rachel, and their relationships (roles in the case of users).

 

API Call

GET /users

Example API URL

https://snb.example.com/api/rest/v1.0/users?enabled=true&page%5Boffset%5D=0&page%5Blimit%5D=20

API Response Body

{
  "links": {
    "self": "https://snb.example.com/api/rest/v1.0/users?q=r&enabled=true&page[offset]=0&page[limit]=20",
    "first": "https://snb.example.com/api/rest/v1.0/users?q=r&enabled=true&page[offset]=0&page[limit]=20"
  },
  "data": [
    {
      "type": "user",
      "id": "100",
      "links": {
        "self": "https://snb.example.com/api/rest/v1.0/users/100"
      },
      "attributes": {
        "createdAt": "2023-07-26T15:00:48.649Z",
        "userId": "100",
        "userName": "ross.geller@monh.com",
        "email": "ross.geller@monh.com",
        "firstName": "Ross",
        "lastName": "geller",
        "country": "USA",
        "organization": "monh",
        "isEnabled": true
      },
      "relationships": {
        "roles": {
          "data": [
            {
              "type": "role",
              "id": "3",
              "meta": {
                "links": {
                  "self": "https://snb.example.com/api/rest/v1.0/roles/3"
                }
              }
            }
          ]
        },
        "systemGroups": {
          "links": {
            "self": "https://snb.example.com/api/rest/v1.0/users/100/systemGroups"
          }
        }
      }
    },
    {
      "type": "user",
      "id": "101",
      "links": {
        "self": "https://snb.example.com/api/rest/v1.0/users/101"
      },
      "attributes": {
        "createdAt": "2023-07-26T15:17:24.693Z",
        "userId": "101",
        "userName": "rachel.green@centralperk.com",
        "email": "rachel.green@centralperk.com",
        "firstName": "Rachel",
        "lastName": "Green",
        "country": "USA",
        "organization": "centralperk",
        "isEnabled": true
      },
      "relationships": {
        "roles": {
          "data": [
            {
              "type": "role",
              "id": "3",
              "meta": {
                "links": {
                  "self": "https://snb.example.com/api/rest/v1.0/roles/3"
                }
              }
            }
          ]
        },
        "systemGroups": {
          "links": {
            "self": "https://snb.example.com/api/rest/v1.0/users/101/systemGroups"
          }
        }
      }
    }
  ],
  "included": [
    {
      "type": "role",
      "id": "3",
      "links": {
        "self": "https://snb.example.com/api/rest/v1.0/roles/3"
      },
      "attributes": {
        "id": "3",
        "name": "Standard User"
      }
    },
    {
      "type": "role",
      "id": "4",
      "links": {
        "self": "https://snb.example.com/api/rest/v1.0/roles/4"
      },
      "attributes": {
        "id": "4",
        "name": "Inventory Admin"
      }
    },
    {
      "type": "role",
      "id": "1",
      "links": {
        "self": "https://snb.example.com/api/rest/v1.0/roles/1"
      },
      "attributes": {
        "id": "1",
        "name": "System Admin"
      }
    },
    {
      "type": "role",
      "id": "2",
      "links": {
        "self": "https://snb.example.com/api/rest/v1.0/roles/2"
      },
      "attributes": {
        "id": "2",
        "name": "Config Admin"
      }
    }
  ]
}


We now see Rachel as a user. There is no change to the included data from our original response as her role was also present for Ross in the previous call.
 



Create experiment and update properties

Overview 

In the following example we will step through the API calls used to create an experiment and subsequently update the description of the newly created experiment.

Components

- Signals Rest APIs

API Calls used

- POST /entities/
- PUT /entities/{eid}/properties

Steps

To create an experiment we will use the POST /entities endpoint. We will create a very basic fresh experiment named "My New Experiment".

Note: This experiment will show in the audit log of whichever user's authentication is used to make the API call. This is true for both API Key authorization and Bearer Token authorization.

 

API Request

POST /entities/

Example of API URL

https://snb.example.com/api/rest/v1.0/entities

API Request Body

{
  "data": {
    "type": "experiment",
    "attributes": {
      "name": "My New Experiment"
    }
  }
}

API Response Body

{
  "links": {
    "self": "https://snb.example.com/api/rest/v1.0/entities/experiment:52afbf7c-8dd2-4b5d-9060-07905f446370"
  },
  "data": {
    "type": "entity",
    "id": "experiment:52afbf7c-8dd2-4b5d-9060-07905f446370",
    "links": {
      "self": "https://snb.example.com/api/rest/v1.0/entities/experiment:52afbf7c-8dd2-4b5d-9060-07905f446370"
    },
    "attributes": {
      "id": "experiment:52afbf7c-8dd2-4b5d-9060-07905f446370",
      "eid": "experiment:52afbf7c-8dd2-4b5d-9060-07905f446370",
      "name": "My New Experiment",
      "description": "",
      "createdAt": "2024-02-05T20:04:48.419Z",
      "editedAt": "2024-02-05T20:04:48.419Z",
      "type": "experiment",
      "state": "open",
      "digest": "60367023",
      "fields": {
        "Description": {
          "value": ""
        },
        "Name": {
          "value": "My New Experiment"
        }
      },
      "flags": {
        "canEdit": true
      }
    },
    "relationships": {
      "createdBy": {
        "links": {
          "self": "https://snb.example.com/api/rest/v1.0/users/100"
        },
        "data": {
          "type": "user",
          "id": "100"
        }
      },
      "editedBy": {
        "links": {
          "self": "https://snb.example.com/api/rest/v1.0/users/100"
        },
        "data": {
          "type": "user",
          "id": "100"
        }
      },
      "owner": {
        "links": {
          "self": "https://snb.example.com/api/rest/v1.0/users/100"
        },
        "data": {
          "type": "user",
          "id": "100"
        }
      },
      "pdf": {
        "links": {
          "self": "https://snb.example.com/api/rest/v1.0/entities/experiment:52afbf7c-8dd2-4b5d-9060-07905f446370/pdf"
        }
      }
    }
  },
  "included": [
    {
      "type": "user",
      "id": "100",
      "links": {
        "self": "https://snb.example.com/api/rest/v1.0/users/100"
      },
      "attributes": {
        "userId": "100",
        "userName": "user.name@example.com",
        "flags": {
          "isSystemStandardUser": true
        },
        "email": "user.name@ example.com",
        "firstName": "User",
        "lastName": "Name",
        "isEnabled": true
      },
      "relationships": {
        "systemGroups": {
          "links": {
            "self": "https://snb.example.com/api/rest/v1.0/users/100/systemGroups"
          }
        }
      }
    }
  ]
}


In our successful response we see the basic data about our new experiment, including the name we set "My New Experiment", and can see additional details about the user who created the experiment.

Now we have decided that we want to add a description property via the API (this could have also been accomplished during creation but for our example we will be adding it immediately after the fact). We will accomplish this with the PUT /entities/{eid}/properties API request. We will leverage the digest value, 60367023 from our successful creation so that ensure we are not out of sync. Learn more about digests here.

NOTE: If the experiment we have just created is edited in any way between our initial creation and request to add a description the digest value will have changed. We can always get the latest digest by fetching our experiment using the GET /entities/{eid}

 

API Request

PUT /entities/{eid}/properties

Example API URL

https://snb.example.com/api/rest/v1.0/entities/experiment%3A52afbf7c-8dd2-4b5d-9060-07905f446370/properties?digest=60367023

API Request Body

{
  "data": [
    {
      "attributes": {
        "name": "Description",
        "value": "This is the description of our new experiment"
      }
    }
  ]
}

API Response Body

{
  "links": {
    "self": "https://snb.example.com/api/rest/v1.0/entities/experiment:52afbf7c-8dd2-4b5d-9060-07905f446370/properties"
  },
  "data": [
    {
      "type": "property",
      "id": "201616",
      "meta": {
        "definition": {
          "type": "text",
          "attribute": {
            "id": "1",
            "name": "Text",
            "type": "text",
            "counts": {
              "templates": {}
            }
          },
          "flags": {
            "isRequired": true,
            "canEdit": true
          }
        }
      },
      "attributes": {
        "id": "201616",
        "name": "Name",
        "value": "My New Experiment",
        "values": [
          "My New Experiment"
        ]
      },
      "relationships": {
        "attribute": {
          "links": {
            "self": "https://snb.example.com/api/rest/v1.0/attributes/1"
          },
          "data": {
            "type": "attribute",
            "id": "1"
          }
        },
        "owner": {
          "links": {
            "self": "https://snb.example.com/api/rest/v1.0/entities/experiment:52afbf7c-8dd2-4b5d-9060-07905f446370"
          },
          "data": {
            "type": "entity",
            "id": "experiment:52afbf7c-8dd2-4b5d-9060-07905f446370"
          }
        }
      }
    },
    {
      "type": "property",
      "id": "201617",
      "meta": {
        "definition": {
          "type": "text",
          "attribute": {
            "id": "1",
            "name": "Text",
            "type": "text",
            "counts": {
              "templates": {}
            }
          },
          "flags": {
            "canEdit": true
          }
        }
      },
      "attributes": {
        "id": "201617",
        "name": "Description",
        "value": "This is the description of our new experiment",
        "values": [
          "This is the description of our new experiment"
        ]
      },
      "relationships": {
        "attribute": {
          "links": {
            "self": "https://snb.example.com/api/rest/v1.0/attributes/1"
          },
          "data": {
            "type": "attribute",
            "id": "1"
          }
        },
        "owner": {
          "links": {
            "self": "https://snb.example.com/api/rest/v1.0/entities/experiment:52afbf7c-8dd2-4b5d-9060-07905f446370"
          },
          "data": {
            "type": "entity",
            "id": "experiment:52afbf7c-8dd2-4b5d-9060-07905f446370"
          }
        }
      }
    }
  ],
  "included": [
    {
      "type": "attribute",
      "id": "1",
      "links": {
        "self": "https://snb.example.com/api/rest/v1.0/attributes/1"
      },
      "attributes": {
        "id": "1",
        "name": "Text",
        "type": "text",
        "counts": {
          "templates": {}
        }
      }
    },
    {
      "type": "entity",
      "id": "experiment:52afbf7c-8dd2-4b5d-9060-07905f446370",
      "links": {
        "self": "https://snb.example.com/api/rest/v1.0/entities/experiment:52afbf7c-8dd2-4b5d-9060-07905f446370"
      },
      "attributes": {
        "type": "experiment",
        "eid": "experiment:52afbf7c-8dd2-4b5d-9060-07905f446370",
        "name": "My New Experiment",
        "digest": "54905888",
        "fields": {
          "Description": {
            "value": "This is the description of our new experiment"
          },
          "Name": {
            "value": "My New Experiment"
          }
        }
      }
    }
  ]
}


In our success response we see the properties of our new experiment including our updated description property.

 



Sample Registration Flow

Overview

In this example we will setup a Sample registration flow to be used directly from a Signals experiment. To accomplish this we will use an External action in conjunction with our REST API to collect sample information, provide a UI action to register the sample externally, and once registered update our Sample in signals with registration details.

Additionally we will incorporate workflow settings that ensure samples are both registered and up to date with the external LIMS.

 

Components

- Signals
  - External Actions
  - Signals REST APIs
- End User
  - External Server setup
 

 Prerequisites

- External Action for Sample entities is created and enabled
  - External Actions
  - The parameter name is sampleId
- An external server has been setup to handle External Action

  - External Server setup
Workflow settings for signing experiments enabled by an administrator:


Architecture Diagram

API Calls used

GET /samples/{sampleId}/properties
GET /samples/{sampleId}/properties/digests.self
GET /samples/{sampleId}/properties/digests.external
PATCH /samples/{sampleId}/properties
PATCH /samples/{sampleId}/properties/digests.external

Steps

With our external action configured (named "Register with External LIMS" in our case) for our samples table we will take this action on a Sample to begin the workflow.

First we will take a look at an example sampleIdthat is part of the URL from our External Action:

sample:6eaf9002-3bc7-4f9c-a2fc-bffdfb987cd7

In our external server we will use this to look up the properties of our sample. We will use these properties to both register our sample in our external LIMS as well as letting Signals know that we have done so. The later will ensure that all our samples are registered and up to date with our External LIMS as part of our signing workflow.

To start we look at the properties of our sample. We will use the full GET /samples/{sampleId}/properites for demonstration purposes. You can specify specific parameters if you know the specific property you are interested in.

API Call:

GET /samples/{sampleId}/properties

Example API URL:

GET https://snb.example.com/api/rest/v1.0/samples/sample:6eaf9002-3bc7-4f9c-a2fc-bffdfb987cd7/properties

Partial API Response Body:

{
  ...
  "data": [
    {
      "type": "property",
      "id": "b718adec-73e0-3ce3-ac72-0dd11a06a308",
      ...
      "attributes": {
        "id": "b718adec-73e0-3ce3-ac72-0dd11a06a308",
        "name": "ID",
        "content": {
          "value": "Sample-158"
        }
      },
      ...
    },
    ...
    {
      "type": "property",
      "id": "digests.self",
      ...
      "attributes": {
        "id": "digests.self",
        "content": {
          "value": "48ac6e467af8aebe152be89895cb0a81ada157bd632809528bc07c993b4bf753"
        }
      },
      ...
    },
    {
      "type": "property",
      "id": "digests.external",
      ...
      "attributes": {
        "id": "digests.external"
      },
      ...
    },
    {
      "type": "property",
      "id": "sampleId",
      ...
      "attributes": {
        "id": "sampleId",
        "name": "ID",
        "content": {
          "name": "Sample-158",
          "type": "sample",
          "value": "sample:6eaf9002-3bc7-4f9c-a2fc-bffdfb987cd7"
        }
      },
      ...
    },
    ...
    {
      "type": "property",
      "id": "5",
      ...
      "attributes": {
        "id": "5",
        "name": "Chemical Name",
        "content": {
          "value": "benzocaine"
        }
      },
      ...
    },
    ...
  ],
  "included": [
    ...
  ]
}

In our partial JSON response body we are focusing on some key properties we will use to accomplish our workflow.

- digests.self
     - This represents the current state of the Sample inside of Signals Notebook. Any change to the Sample will update this value.
- digests.external
     - This represents the state of the Sample in our External System. Initially, and seen in our response, this is not set. Once we update this property it will be used to compare the state of the Sample in Signals Notebook compared to the External System.
- ID
- Chemical Name

The full response contains all the properties of our sample, as well as additional information in the includedarray that may be required to register a Sample with your External LIMS. For example you may need the ID and Chemical Name to register with your External LIMS.

Once registered in your external LIMS we want to update our external digest of our Sample so that Signals knows that we have registered the Sample and what its state, or digest, was at the time of registration.

Our digest from the above example response:

48ac6e467af8aebe152be89895cb0a81ada157bd632809528bc07c993b4bf753

API Call:

PATCH /samples/{sampleId}/properties/digests.external

Example API URL:

PATCH https://snb.example.com/api/rest/v1.0/samples/sample:6eaf9002-3bc7-4f9c-a2fc-bffdfb987cd7/properties/digests.external

Example Request Body:

{
"data": {
  "attributes": {
    "content": {
      "value": "48ac6e467af8aebe152be89895cb0a81ada157bd632809528bc07c993b4bf753"
      }
    }
  }
}

At this point, the sample is considered registered and the UI will keep track of any changes made to the sample that would render the registration out of date, in which case the UI will show a warning. If we make any changes Signals will warn us (if configured to do so) that our Sample is out of date with our external system and needs to be re-registered externally by repeating the steps in this example.

Examples of warnings in the UI:

 



Automated Archival for Closed Experiments

Overview

In this example we will setup an automated process for generating PDF copies of experiments when they are closed. To accomplish this we will make use of our API and External Notifications. When setup an external notificaiton will fire for a "Sign and Close" event on an experiment. Once we recieve this we will kick of the process to generate a PDF, download it, and save it to a folder.

We will make use of the asynchronous PDF generation process. This is the recommended path for generation of PDFs to prevent time outs during the PDF generation of larger experiments.

Components

- Signals

- External Notifications

- Signals REST API

- End User

- External Server Setup

Prerequisites

- External Notifications for "Sign and Close" events are enabled

- Setup Guide

- An external server has been setup to handle notifications

- External Server setup

Architecture Diagram

API Calls Used

PUT /entities/export/pdf
HEAD /entities/export/pdf/{fileId}
GET /entities/export/pdf{fileId}

Steps

First in our external server we handle the JSON body of a notification to determine it is for a "Sign and Close" event and which experiment was closed. This external notification will provide us the starting point for our PDF generation.

To parse this out we will examine the JSON Object from the notification we receive.

 

JSON Notification Body

{
  "links": {
    ...
  },
  "data": {
    "type": "notification",
    "id": "519",
    "links": {
      "self": "https://snb.example.com/api/rest/v1.0/notifications/519"
    },
    "attributes": {
      "id": "519",
      "createdAt": "2023-12-13T20:42:50.427Z",
      "type": "close",
      "isDismissed": true,
      "isRead": false,
      "isFlagged": false,
      "comment": ""
    },
    "relationships": {
      "createdBy": {
        "links": {
          "self": "https://snb.example.com/api/rest/v1.0/users/100"
        },
        "data": {
          "type": "user",
          "id": "100"
        }
      },
      "entity": {
        "links": {
          "self": "https://snb.example.com/api/rest/v1.0/entities/experiment:d5dc8e92-580d-41e2-9c65-99726294966c"
        },
        "data": {
          "type": "entity",
          "id": "experiment:d5dc8e92-580d-41e2-9c65-99726294966c"
        }
      }
    }
  },
  "included": [
    ...
  ]
}

The first piece of information we want to extract is the type from data -> attributes -> type to very what type of notification we are receiving. For Sign and Close we are looking for close.

Next we will extract the Experiments entity ID from data -> relationships -> entity -> data -> id. In our example response this is:

experiment:d5dc8e92-580d-41e2-9c65-99726294966c

Now using our Experiments entity ID we will make an API call to begin the PDF generation. Our goal is to get the file id of the PDF being generated to use in subsequent API calls.

API Call

PUT /entities/export/pdf

Example API URL

https://snb.example.com/api/rest/v1.0/entities/export/pdf?eid=experiment:d5dc8e92-580d-41e2-9c65-99726294966c&attachments=true"

Partial API Response Body

{
   "links": {
     ...
    },
   "data": {
     "type": "pdf",
     "id": "1b654ddf-17c7-4ec2-913c-9f9744358b4f",
     "links": {
       "self": "https://snb.example.com/api/rest/v1.0/entities/export/pdf?eid=experiment:d5dc8e92-580d-41e2-9c65-99726294966c"
     },
     "attributes": {
       "type": "pdf",
       "fileId": "1b654ddf-17c7-4ec2-913c-9f9744358b4f",
       "fileName": "f720-1.pdf"
     },
     "relationships": {
       ...
   },
   "included": [
     ...
   ]
 }

From our response we parse out data -> attribute -> fileId. In our example response: 1b654ddf-17c7-4ec2-913c-9f9744358b4f

Now with our file ID we will check to see if our PDF has completed generating. This is done by checking the content-length from the HTTP header returned from our next API call. If the content-length is > 0 we have completed generating our PDF, otherwise we will wait 5 seconds and try again.

API Call

HEAD /entities/export/pdf/{fileId}

Example API URL

https://snb.example.com/api/rest/v1.0/entities/export/pdf/1b654ddf-17c7-4ec2-913c-9f9744358b4f

Partial API Response Header

access-control-allow-credentials: true 
access-control-allow-headers: Content-Type,Access-Control-Allow-Headers,Authorization,X-Requested-With 
access-control-allow-methods: GET,POST,PUT,PATCH,HEAD,DELETE,OPTIONS 
cache-control: no-cache,no-store,must-revalidate 
content-length: 35640 
date: Tue,02 Jan 2024 20:38:32 GMT 
expires: -1 
pragma: no-cache 
server: nginx 
server-timing: intid;desc=efe2a92b4066b6f5 
...

If the content-length in our HEAD request response is > 0 we request to download the generated PDF with the API. Otherwise we repeat our HEAD request check for content-length.

With our PDF generated all we need to do is download it and save it.

API Call

GET /entities/export/pdf{fileId}

Example API URL

https://snb.example.com/api/rest/v1.0/entities/export/pdf/1b654ddf-17c7-4ec2-913c-9f9744358b4f

This API will return a response with a content-type of application/pdf. Using whichever method appropriate for your external service you can download the PDF from the response.

Partial Response Header

...
 content-disposition: attachment; filename="f720-1.pdf"; filename*=utf-8''f720-1.pdf
 content-type: application/pdf 
...

You have now configured your external server to asynchronously download any signed and closed experiments at the time of signing.

NOTE: To further ensure you never miss a "Sign and Close" notification read more about Pull Notifications
 



Additional Signing Compliance

Overview

In this example we will setup additional compliance checking that may be unique to your organization. This will serve as a way to prevent signing and closing of experiments that are missing requirements. We will accomplish this with an External Action that opens an external site at the time of sign and close. For our example the additional compliance will verify that a safety document is included in the experiment. If it is included the page can be dismissed and signing will complete. Otherwise the external site will provide a warning to the end user that they need to add their safety document and when dismissed will prevent the sign and close event from completing.

Components

- Signals

     - External Actions
     - Signals REST APIs

- End User

     - External Server setup
     - External Site that presents a message based on the existence of an excel entity named "Safety Sheet"
          - Utilizing External Actions Messages

Prerequisites

- External Action for "Sign and Close" events are enabled      - Setup Guide

- An external site has been setup to handle our "Sign and Close" action
     - External Server setup      - The external site will warn the end user if they are missing required documentation

Architecture Diagram

API Calls Used

GET /entities/{eid}/children

Steps

With our external action configured for our "Sign and Close" on an Experiment we will look at the Experiments children during signing in order to check for an excel entity named "Safety Sheet". If successful we will show the user a success message and a continue button to dismiss our external site with closeAndContinue. If we do not find the excel sheet we will display an error and dismiss our external site with closeAndAbort.

First we will take a look at an example experiment EID that is part of the URL from our External Action:

experiment:33f3ce00-cdcb-4c52-8d25-6ce1b37134c6

In our external server we will use this to look up information of our Experiment. We will look for the children and verify there is an excel file and leverage the included object to find a bit more information about the child, specifically its Name.

 

API Call

GET /entity/{eid}

Example API URL:

GET https://snb.example.com/api/rest/v1.0/entity/experiment:33f3ce00-cdcb-4c52-8d25-6ce1b37134c6

Partial API Response Body

{
  ...
  "data": {
    "type": "entity",
    "id": "experiment:33f3ce00-cdcb-4c52-8d25-6ce1b37134c6",
    ...
    "relationships": {
      ...
      "children": {
        ...
        "data": [
          ...
          {
            "type": "entity",
            "id": "excel:5badd272-d234-4e2a-9c1f-c5a447ac0896",
            "meta": {
              "links": {
                "self": "https://snb.example.com/api/rest/v1.0/entities/excel:5badd272-d234-4e2a-9c1f-c5a447ac0896"
              }
            }
          },
          ...
        ]
      },
      ...
    }
  },
  "included": [
    ...
    {
      "type": "entity",
      "id": "",
      "links": {
        "self": "https://snb.example.com/api/rest/v1.0/entities/excel:5badd272-d234-4e2a-9c1f-c5a447ac0896"
      },
      "attributes": {
        "type": "excel",
        "eid": "excel:5badd272-d234-4e2a-9c1f-c5a447ac0896",
        "name": "Safety Sheet",
        "digest": "56497474",
        "fields": {
          "Description": {
            "value": ""
          },
          "Name": {
            "value": "Safety Sheet"
          }
        }
      }
    },
    ...
  ]
}

In our partial response we can see the children as part of the relationships in the data object. This contains all our Experiments children. We can look through this to find an excel entity. In our example we see the following entity: excel:5badd272-d234-4e2a-9c1f-c5a447ac0896

We then can look for that entity within our included array (alternatively we could make a second call to GET /entity/{eid} with our excel entities id) for the name of our excel file. If it is "Safety Sheet" we will allow the user to dismiss the external site and complete the signing using the External Action messages:

window.parent.postMessage(
['closeAndContinue', []],
'https://snb.example.com/'
)

This will complete the signing flow successfully and close the experiment.

In our example case we did find it, however, if we were to not find it we would dismiss our external site with the following message:

window.parent.postMessage(
['closeAndAbort', []],
'https://snb.example.com/'
)

This will abort the signing flow leaving our experiment open.