Code-Based Inspections
Subscription Tier Required
This feature requires the Premier subscription tier or higher.
There are times when the data collected depends on multiple, dynamic factors that may change over time. This makes it difficult to create static Inspections. In this guide, you will create an Inspection which reads from a mock API to create the required tests at runtime.
You should be familiar with the principles of Inspection Scripting before getting started.
Setup
Mock Data Source
Before beginning, you will need to create a data source to read from. For the purposes of this guide, this will be a static function. In a production environment, this data source could take the form of:
- A File uploaded to GS
- A third party API
- A local database
To create the function:
- Navigate to the list of Library Scripts.
- Press the Add button.

- Fill in the Name with Fetch Data.
- Press the Save button.

-
Fill in the Library Script.
/** * @typedef {Object} CurrentChecks * @property {string} process * @property {string} part * @property {string[]} characteristics * @property {string[]} passFail * @property {Record<string, string | number>} traceability */ /** * Randomly selects between two possible sample configurations. * @returns Promise<CurrentChecks> */ async function getCurrentChecks() { if (Math.random() < 0.5) { return { process: 'Final Inspection', part: 'AD-50', traceability: { Shift: 'Shift 1' }, characteristics: [ 'Width', 'Height', 'Length' ], passFail: [ 'Scratch', 'Dent' ] }; } else { return { process: 'Final Inspection', part: 'AD-70', traceability: { Shift: 'Shift 2' }, characteristics: [ 'Width', 'Height', ], passFail: [ 'Scratch', 'Dent', 'Rust' ] }; } }
Information
The Library Script includes a JS Doc comment which lets the Code Editor know what type the variable returns. While not strictly necessary, this will be useful later for auto-complete when calling the function.
This script assumes that you have:
- A Process named Final Inspection.
- Parts named AD-50 and AD-70.
- A Traceability named Shift.
- Characteristics for Part AD-50 named Width, Height, and Length.
- Characteristics for Part AD-70 named Width and Height.
- Defects named Scratch, Dent, and Rust.
Feel free to update the script to use the Processes, Parts, Traceability, Characteristics, and Defects which exist in your GS account.
Inspection
You will also need to create an empty Inspection to dynamically populate.
- Navigate to the Inspection list.
- Press the Add button.

- Fill in the Name field with Dynamic Inspection.
- Press the Save button.

- Select the Scripts action.

- Add your Library Script.
- Add a new Inspection Script.

Script
At this point, you should see the Code Editor. Fill in the Name field with the value Create Inspection, and add the following to the script window:
This creates a block of code which will be executed once the Inspection has fully loaded and is ready to interact with.
Read From the API
Next, retrieve information about what is currently running on the production line. Inside of the function bound into onReady, add code which calls the Library Script created earlier:
Information
Hovering over the data variable should show the type of the variable returned. If it does not, make sure that the Library Script is included, and that there are no errors in the Library Script.
Create the Sub-Inspection
Although the Inspection comes pre-loaded with an empty Sub-Inspection which could be populated, for the purpose of this exercise you will create a new Sub-Inspection. After retrieving the current checks, create a new Sub-Inspection:
const data = await getCurrentChecks();
await gsApi.inspection.addSubInspection({
name: 'Collect Data',
scriptId: 'collect-data',
});
Next, get a reference to the created Sub-Inspection. Outside of the onReady function, create a reference to the Sub-Inspection API:
const subInspectionApi = gsApi.inspection.subInspection('collect-data');
gsApi.inspection.onReady(async () => {
Information
Sub-Inspection and Test APIs do not check if the Sub-Inspection or Test exists when they're created, but when they're used. This allows the creation of the subInspectionApi variable at the beginning of the script, even though the collect-data Sub-Inspection does not exist yet.
Create the Tests
Now that the Sub-Inspection is created, tests may be added to it. The mock checks require a:
- Process Test
- Part Test
- Traceability Test for each Traceability
- SPC Test for each Characteristic
- Chart Test for each SPC Test
- Pass/Fail Test for each Defect
Process Test
After the creation of the Sub-Inspection, retrieve the ID of the Process and set it as the value of a newly created Process Test. Operators should not be able to modify the value, so it will also be disabled:
await gsApi.inspection.addSubInspection({
name: 'Collect Data',
scriptId: 'collect-data',
});
const processId = await gsApi.entity.getProcessIdByName(data.process);
await subInspectionApi.addProcessTest({
label: 'Process',
processId: processId,
enabled: false
});
Information
If the location property is not provided when creating a Test, it will be added in the next available position determined by the Inspection setting New Test Layout.
Part Test
The same operation will be performed for adding the part test:
const partId = await gsApi.entity.getPartIdByName(data.part);
await subInspectionApi.addPartTest({
label: 'Part',
partId: partId,
enabled: false
});
Traceability
Traceability will be added very similarly to Part and Process. Unlike Part and Process, multiple Traceability may be added. To do this, loop over the Traceability:
for (const key in data.traceability) {
const traceabilityId = await gsApi.entity.getTraceabilityIdByName(key);
await subInspectionApi.addTraceabilityTest({
label: key,
traceabilityId: traceabilityId,
value: data.traceability[key],
enabled: false
});
}
Information
Many Javascript guides use Object.keys(objects).forEach() to loop over objects instead of for (const key in object). .forEach generally does not work well with asynchronous code, and so standard for loops should be preferred. As a general rule, if the code involves await, it will not work correctly with forEach.
Exercise
Try modifying the script to allow collecting some Traceability from the operator.
SPC and Charts
SPC Tests and Charts should lay out side by side for clarity. This will involve manually specifying the layout for the Tests and Charts. First, figure out how many rows have been added to the Sub-Inspection. This is one row for each Part and Process Test, plus an additional row for each Traceability Test:
Then loop through the Characteristics, adding SPC Tests. The SPC Tests will be given a Script ID to be referenced by the Chart Test, and each SPC Test will go onto a new row:
for (let i = 0; i < data.characteristics.length; i++) {
const characteristicId = await gsApi.entity.getSPCCharacteristicIdByName(data.characteristics[i], data.part);
const spcTestId = 'spc-test-' + i.toString();
await subInspectionApi.addSPCTest({
label: data.characteristics[i],
spcCharacteristicId: characteristicId,
width: 6,
scriptId: spcTestId,
location: {
insertMode: 'insertIntoNewRow',
column: 0
}
});
}
Inside the same loop, also add a Chart Test for each SPC Test. The Chart Test will go into the same row as the SPC Test, and then increment the row count:
for (let i = 0; i < data.characteristics.length; i++) {
...
await subInspectionApi.addChartTest({
label: '',
linkedTests: [spcTestId],
chartType: 'control',
dataType: 'spc',
width: 6,
location: {
insertMode: 'appendToExistingRow',
row: numberOfRows,
column: 6
}
})
numberOfRows++;
}
Pass/Fail Tests
Finally, add the Pass/Fail tests for each Defect:
for (let i = 0; i < data.passFail.length; i++) {
const defectId = await gsApi.entity.getDefectIdByName(data.passFail[i]);
await subInspectionApi.addPassFailTest({
label: data.passFail[i],
defectId: defectId
});
}
Navigate to the Inspection
If you run the Inspection now, it will take you to the list of Sub-Inspections. Instead, update the Inspection to remove the first, empty Sub-Inspection and to automatically navigate to the created Sub-Inspection:
await gsApi.inspection.removeSubInspection('subi 1');
await gsApi.inspection.updateProperties({
autoLoadFirstSubInspection: true
});
Information
The automatically created Sub-Inspection will always have a Script ID of subi 1 unless it is manually changed.
After Submit
There are multiple places that operators may be sent after completing or cancelling a Sub-Inspection. For the purpose of this guide, we will send them back to the list of Inspections.
Outside of the onReady function, add an event after the Sub-Inspection has ended to send the user to the Inspection list:
const subInspectionApi = gsApi.inspection.subInspection('collect-data');
subInspectionApi.onAfterEnd(async (e) => {
await gsApi.inspection.goToInspectionList();
});
gsApi.inspection.onReady(async () => {
Information
When binding to different events, the order in which the events are bound does not matter. Binding to onAfterEnd of the Sub-Inspection could be done at the end of the script instead of before onReady without changing the behavior.
Run the Inspection
Press the Save and Run button to open the Inspection in a new tab. If everything worked correctly, you should be able to complete the Sub-Inspection, and completing the Sub-Inspection should navigate you back to the list of Inspections. Run the Inspection several times and make sure that it collects data for both different Characteristics and Defects.
Optimization: Parallel Operations
Warning
This is an advanced topic which requires a deep understanding of asynchronous programming, Promises, and lexical scoping.
Writing code with async / await tends to be easier to read, but it can lead to slowdowns. In the above example, we look up each entity ID using the entity's name. Each one of these sequential API calls adds additional overhead, slowing down the construction of the Inspection.
Using await, the code is run sequentially. This means that the Part ID is not retrieved until the Process ID has finished retrieving. Instead, the entity IDs can be retrieved in parallel, dramatically speeding up the process. This will be done by firing requests in parallel, and then waiting for all of them to complete.
To begin, parallelize fetching the Process ID and Part ID:
let processId;
let partId;
await Promise.all([
gsApi.entity.getProcessIdByName(data.process).then(id => processId = id),
gsApi.entity.getPartIdByName(data.part).then(id => partId = id)
]);
This fetches both the Process and Part at the same time, then waits for them to finish. This is one instance when using .then is advantageous over await. Because processId and partId were originally declared in the outer scope, when they are assigned inside of the .then, they are available in original outer scope where they were declared.
Do not forget to remove the original declarations of processId and partId.
Next, add in the Traceability IDs:
let processId;
let partId;
const traceabilityIds = {};
await Promise.all([
gsApi.entity.getProcessIdByName(data.process).then(id => processId = id),
gsApi.entity.getPartIdByName(data.part).then(id => partId = id),
...Object.keys(data.traceability).map(traceName => gsApi.entity.getTraceabilityIdByName(traceName).then(id => traceabilityIds[traceName] = id)),
]);
This is slightly more complicated. Instead of assigning a single variable, this code creates a mapping of the Traceability name to Traceability ID. Each key in the original object is mapped into a Promise, and then the resulting Promise array is spread into the array which is passed into Promise.all.
To use the resulting mapping of Traceability names to IDs later in the code, replace:
With the new code:
Once the Traceability has been configured, we can move on to the Characteristics. The following code will set up a list of Characteristic IDs that are in the same order as the Characteristic names:
let processId;
let partId;
const traceabilityIds = {};
const characteristicIds = new Array(data.characteristics.length);
await Promise.all([
gsApi.entity.getProcessIdByName(data.process).then(id => processId = id),
gsApi.entity.getPartIdByName(data.part).then(id => partId = id),
...Object.keys(data.traceability).map(traceName => gsApi.entity.getTraceabilityIdByName(traceName).then(id => traceabilityIds[traceName] = id)),
...data.characteristics.map((charName, index) => gsApi.entity.getSPCCharacteristicIdByName(charName, data.part).then(id => characteristicIds[index] = id)),
]);
The Characteristic names are once again mapped into an array of Promises, and then spread into the Promise.all array. Note that the original array is constructed with the necessary length (using new Array()), and the retrieved Characteristic IDs are stored at the appropriate index instead of adding to the array using the push operator. This is done to preserve the original order of the Characteristics, as there is no guarantee that the code in the .then will run in the correct order.
As with Traceability, use the correct array later in the code by replacing:
const characteristicId = await gsApi.entity.getSPCCharacteristicIdByName(data.characteristics[i], data.part);
With:
Finally, the same procedure is done for the Defects:
let processId;
let partId;
const traceabilityIds = {};
const characteristicIds = new Array(data.characteristics.length);
const defectIds = new Array(data.passFail.length);
await Promise.all([
gsApi.entity.getProcessIdByName(data.process).then(id => processId = id),
gsApi.entity.getPartIdByName(data.part).then(id => partId = id),
...Object.keys(data.traceability).map(traceName => gsApi.entity.getTraceabilityIdByName(traceName).then(id => traceabilityIds[traceName] = id)),
...data.characteristics.map((charName, index) => gsApi.entity.getSPCCharacteristicIdByName(charName, data.part).then(id => characteristicIds[index] = id)),
...data.passFail.map((defectName, index) => gsApi.entity.getDefectIdByName(defectName).then(id => defectIds[index] = id))
]);
And replace:
with:
Final Code
The final code, excluding the advanced optimization, is:
const subInspectionApi = gsApi.inspection.subInspection('collect-data');
subInspectionApi.onAfterEnd(async (e) => {
await gsApi.inspection.goToInspectionList();
});
gsApi.inspection.onReady(async () => {
const data = await getCurrentChecks();
await gsApi.inspection.addSubInspection({
name: 'Collect Data',
scriptId: 'collect-data',
});
const processId = await gsApi.entity.getProcessIdByName(data.process);
await subInspectionApi.addProcessTest({
label: 'Process',
processId: processId,
enabled: false
});
const partId = await gsApi.entity.getPartIdByName(data.part);
await subInspectionApi.addPartTest({
label: 'Part',
partId: partId,
enabled: false,
});
for (const key in data.traceability) {
const traceabilityId = await gsApi.entity.getTraceabilityIdByName(key);
await subInspectionApi.addTraceabilityTest({
label: key,
traceabilityId: traceabilityId,
value: data.traceability[key],
enabled: false
});
}
let numberOfRows = 2 + Object.keys(data.traceability).length;
for (let i = 0; i < data.characteristics.length; i++) {
const characteristicId = await gsApi.entity.getSPCCharacteristicIdByName(data.characteristics[i], data.part);
const spcTestId = 'spc-test-' + i.toString();
await subInspectionApi.addSPCTest({
label: data.characteristics[i],
spcCharacteristicId: characteristicId,
width: 6,
scriptId: spcTestId,
location: {
insertMode: 'insertIntoNewRow',
column: 0
}
});
await subInspectionApi.addChartTest({
label: '',
linkedTests: [spcTestId],
chartType: 'control',
dataType: 'spc',
width: 6,
location: {
insertMode: 'appendToExistingRow',
row: numberOfRows,
column: 6
}
})
numberOfRows++;
}
for (let i = 0; i < data.passFail.length; i++) {
const defectId = await gsApi.entity.getDefectIdByName(data.passFail[i]);
await subInspectionApi.addPassFailTest({
label: data.passFail[i],
defectId: defectId
});
}
await gsApi.inspection.removeSubInspection('subi 1');
await gsApi.inspection.updateProperties({
autoLoadFirstSubInspection: true
});
});

