> For the complete documentation index, see [llms.txt](https://help.ica.illumina.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://help.ica.illumina.com/project/p-flow/f-pipelines/json-based-input-forms/onfinished.js.md).

# onFinished.js

The **onFinished** JavaScript functionality allows you to execute custom JavaScript code automatically after a pipeline analysis completes. It runs when the analysis completes successfully and provides access to the analysis inputs, outputs, and pipeline settings through a JavaScript execution environment. You can use this to perform post-processing tasks such as linking output files to samples.

## Usage <a href="#onfinishjavascript-gettingstarted" id="onfinishjavascript-gettingstarted"></a>

### Creating an onFinish Script <a href="#onfinishjavascript-creatinganonfinishscript" id="onfinishjavascript-creatinganonfinishscript"></a>

1. Create a JavaScript file named `onFinished.js` in your pipeline.
2. Define a function called `onFinished` that accepts an `input` parameter
3. The function will be called automatically when your analysis completes

## Basic Structure <a href="#onfinishjavascript-basicstructure" id="onfinishjavascript-basicstructure"></a>

```js
function onFinished(input) {
    // Your post-processing logic here
    print("Analysis completed!");
    
    // Access analysis information
    print("Pipeline: " + input.pipeline.name);
    print("Analysis status: " + input.analysis.status);
}
```

## Input Object <a href="#onfinishjavascript-inputobject" id="onfinishjavascript-inputobject"></a>

The `input` parameter provides access to analysis context and results:

```
input.pipeline
```

Information about the pipeline that was executed:

* **name** - Pipeline name
* **version** - Pipeline version string
* **tenant** - Tenant name
* **description** - Pipeline description

```
input.analysis
```

Information about the analysis execution:

* **userReference** - User-defined reference/name for the analysis
* **owner** - Username of the analysis owner
* **tenant** - Tenant name
* **status** - Current analysis status

```
input.currentAnalysisSettings
```

The pipeline input form configuration used for this analysis.

```
input.settingValues
```

A map of the actual input values provided when launching the analysis. Keys correspond to pipeline input parameter codes.

Example:

```js
// Access input parameter values
var sampleId = input.settingValues.sample_id;
var threshold = input.settingValues.quality_threshold;
```

```
input.outputs
```

A map of pipeline output parameters to their resulting data files. Keys are output parameter codes, values are arrays of data objects.

Each data object contains:

* **id** - Data file identifier
* **name** - Data file name
* **path** - Full path to the data file
* **format** - Data format code (e.g., "BAM", "FASTQ", "VCF")
* **size** - File size in bytes (-1 if unknown)
* **folder** - Boolean indicating if this is a folder
* **sampleIds** - Array of sample UUIDs associated with this data

```
input.samples
```

A map of sample objects referenced in the analysis inputs. Keys in the map are sample UUID, and every sample object contains:

* **id** - the UUID identifier
* **name** - Data file name
* **instrumentRunIds** - list of instrument run ids

## Injected Functions <a href="#onfinishjavascript-injectedfunctions" id="onfinishjavascript-injectedfunctions"></a>

The onFinish environment provides a number functions ([searchSamples](#onfinishjavascript-1.searchsamples), [searchData](#onfinishjavascript-2.searchdatas) and [linkDataToSamples](#onfinishjavascript-3.linkdatatosample)) for post-processing operations:

### searchSamples() <a href="#onfinishjavascript-1.searchsamples" id="onfinishjavascript-1.searchsamples"></a>

Search for samples by ID or name.

#### **Syntax:**

{% code overflow="wrap" expandable="true" %}

```js
var samples = searchSamples({
    id: "sample-uuid",        // Optional: Search by sample UUID
    name: "sample-name"       // Optional: Search by exact sample name
});
```

{% endcode %}

#### **Parameters:**

* Object with one or both (at least one parameter must be provided) of:
  * **id** - Sample UUID to search for
  * **name** - Exact sample name to search for

#### **Returns:**

Array of sample objects, where each sample contains:

* **id** - Sample UUID
* **name** - Sample name
* **instrumentRunIds** - Array of instrument run IDs associated with this sample

#### **Example:**

{% code overflow="wrap" expandable="true" %}

```js
// Search by sample ID
var samples = searchSamples({ id: "550e8400-e29b-41d4-a716-446655440000" });

// Search by sample name
var samples = searchSamples({ name: "SAMPLE_001" });

// Check results
if (samples && samples.length > 0) {
    samples.forEach(function(sample) {
        print("Found sample: " + sample.name + " (ID: " + sample.id + ")");
    });
} else {
    print("No samples found");
}
```

{% endcode %}

#### **Error Handling:**

Errors are automatically printed to the logs. The function returns null on error.

***

### searchDatas() <a href="#onfinishjavascript-2.searchdatas" id="onfinishjavascript-2.searchdatas"></a>

Search for data files in your analysis outputs with flexible filtering options.

#### **Syntax:**

{% code overflow="wrap" expandable="true" %}

```js
var datas = searchDatas({
    parent: dataObject,       // Optional: Parent folder/data to search within
    recursive: true,          // Optional: Search recursively (default: true)
    pathRegex: ".*\\.bam",  // Optional: Regex to match file paths
    nameRegex: ".*output.*"   // Optional: Regex to match file names
});
```

{% endcode %}

#### **Parameters:**

Object with optional filters:

* **parent** - Data object to use as parent (searches within this folder/path)
* **recursive** - Boolean, if **true** searches recursively from parent path (default: true)
* **pathRegex** - Regular expression to filter by file path (case-insensitive)
* **nameRegex** - Regular expression to filter by file name (case-insensitive)

#### **Returns:**

Array of data objects, where each data contains:

* **id** - Data file identifier
* **name** - File name
* **path** - Full file path
* **format** - Data format code
* **size** - File size in bytes
* **folder** - Boolean indicating if this is a folder
* **sampleIds** - Array of associated sample UUIDs

#### **Examples:**

{% code overflow="wrap" expandable="true" %}

```js
// Search for all BAM files in outputs
var bamFiles = searchDatas({
    nameRegex: ".*\\.bam$"
});

// Search within a specific output folder
var outputFolder = input.outputs.results[0];
var resultsFiles = searchDatas({
    parent: outputFolder,
    recursive: true
});

// Find files matching a pattern in a specific path
var reportFiles = searchDatas({
    pathRegex: ".*/reports/.*",
    nameRegex: ".*\\.html$"
});

// Process results
if (bamFiles && bamFiles.length > 0) {
    print("Found " + bamFiles.length + " BAM files:");
    bamFiles.forEach(function(file) {
        print("  - " + file.name + " at " + file.path);
    });
}
```

{% endcode %}

#### **Use Cases:**

* Find specific output files by pattern
* Navigate through output folder structures
* Filter files by type or naming convention
* Locate files for further processing

***

### linkDataToSample() <a href="#onfinishjavascript-3.linkdatatosample" id="onfinishjavascript-3.linkdatatosample"></a>

Link an output data file to a sample, establishing the relationship between analysis outputs and samples.

#### **Syntax:**

{% code overflow="wrap" expandable="true" %}

```js
linkDataToSample({
    data: dataObject,      // Required: Data object to link
    sample: sampleObject   // Required: Sample object to link to
});
```

{% endcode %}

#### **Parameters:**

Object with two required properties:

* **data** - Data object (from `input.outputs` or `searchDatas()`)
* **sample** - Sample object (from `input.samples` or `searchSamples()`)

#### **Returns:**

None. The function establishes the link in the system.

#### **Example:**

{% code overflow="wrap" expandable="true" %}

```js
// Link output BAM files to their corresponding samples
var outputBams = input.outputs.output_bam;

if (outputBams && outputBams.length > 0) {
    outputBams.forEach(function(bam) {
        // Extract sample name from BAM filename
        var sampleName = bam.name.replace(/\.bam$/, "");
        
        // Find the corresponding sample
        var samples = searchSamples({ name: sampleName });
        
        if (samples && samples.length > 0) {
            var sample = samples[0];
            
            // Link the BAM file to the sample
            linkDataToSample({
                data: bam,
                sample: sample
            });
            
            print("Linked " + bam.name + " to sample " + sample.name);
        } else {
            print("Warning: No sample found for " + bam.name);
        }
    });
}
```

{% endcode %}

#### **Use Cases:**

* Associate output files with input samples
* Track which files belong to which samples
* Enable sample-centric data organization
* Facilitate downstream sample-based queries

#### **Error Handling:**

If the data or sample cannot be found, or if the linking fails, an error message is automatically printed to the logs.

***

## Full Example <a href="#onfinishjavascript-completeexample" id="onfinishjavascript-completeexample"></a>

This example demonstrates a typical post-processing workflow:

{% code overflow="wrap" expandable="true" %}

```js
function onFinished(input) {
    print("=== Post-Analysis Processing Started ===");
    print("Pipeline: " + input.pipeline.name + " v" + input.pipeline.version);
    print("Analysis: " + input.analysis.userReference);
    
    // 1. Search for BAM files in the outputs
    print("\n--- Searching for BAM files ---");
    var bamFiles = searchDatas({
        nameRegex: ".*\\.bam$"
    });
    
    if (!bamFiles || bamFiles.length === 0) {
        print("No BAM files found in outputs");
        return;
    }
    
    print("Found " + bamFiles.length + " BAM file(s)");
    
    // 2. Link each BAM file to its corresponding sample
    print("\n--- Linking BAM files to samples ---");
    bamFiles.forEach(function(bam) {
        // Extract sample name from filename (assumes format: SAMPLENAME.bam)
        var fileBaseName = bam.name.replace(/\.bam$/, "");
        
        print("Processing: " + bam.name);
        
        // Search for the sample
        var samples = searchSamples({ name: fileBaseName });
        
        if (samples && samples.length > 0) {
            var sample = samples[0];
            
            // Create the link
            linkDataToSample({
                data: bam,
                sample: sample
            });
            
            print("  ✓ Successfully linked to sample: " + sample.name);
        } else {
            print("  ✗ Warning: No matching sample found for " + fileBaseName);
        }
    });
    
    // 3. Generate summary
    print("\n--- Summary ---");
    print("Total outputs processed: " + bamFiles.length);
   
    // List all output parameters
    for (var outputCode in input.outputs) {
        var outputData = input.outputs[outputCode];
        print("Output parameter '" + outputCode + "': " + outputData.length + " file(s)");
    }
    
    print("\n=== Post-Analysis Processing Completed ===");
}
```

{% endcode %}

#### Linking to Sample

link output files to the same sample to which the input file with the same name was linked.

{% code overflow="wrap" expandable="true" %}

```js
function onFinished(input) {
    print("hello, i'm inside the onFinished function");
    print("input = " + input);

    var inputFiles = input.settingValues["in"];

    var outputDatas = searchDatas({});
    print("outputDatas = " + outputDatas);

    for (const outputdata of outputDatas) {
        var inputFile = find(inputFiles, outputdata.name);
        if (inputFile && inputFile.sampleIds) {
            for (const sampleId of inputFile.sampleIds) {
                linkDataToSample({"data": outputdata, "sample": input.samples[sampleId]});
            }
        }
    }
}


function find(inputFiles, name) {
    for (const inputFile of inputFiles) {
        if (inputFile.name == name) {
            return inputFile;
        }
    }
    return null;
}
```

{% endcode %}

#### Linking with Textbox

Linking output files to a sample for which there is a textbox('targetSampleName') for the sample name in the inputform.

{% code expandable="true" %}

```js
function onFinished(input) {
    print("hello, i'm inside the onFinished function");
    print("input = " + input);
    var targetSampleName = input.settingValues["targetSampleName"];
    print("targetSampleName = " + targetSampleName);
    
    var validationErrors = [];

    var outputDatas = searchDatas({"nameRegex": ".*.txt"});
    print("outputDatas = " + outputDatas);
    
    var samples = searchSamples({"name": targetSampleName});
    print("samples = " + samples);
    
    for (const outputdata of outputDatas) {
        for (const sample of samples) {
            linkDataToSample({"data": outputdata, "sample": sample});
        }
    }
}
```

{% endcode %}

#### Linking Output Folders to Samples <a href="#onfinishjavascript-linkingoutputfolderstosamples" id="onfinishjavascript-linkingoutputfolderstosamples"></a>

{% code overflow="wrap" expandable="true" %}

```js
// When analysis outputs contain per-sample folders with multiple files
// Link each folder to its corresponding sample based on folder name

function onFinished(input) {
    print("=== Linking Sample Folders ===");
    
    var sampleFolders = getSampleOutputFolders(input);
    if (!sampleFolders || sampleFolders.length === 0) {
        print("No sample folders found in outputs");
        return;
    }
    
    print("Found " + sampleFolders.length + " output folder(s)");
    
    var successCount = 0;
    var failureCount = 0;
    
    sampleFolders.forEach(function(folder) {
        // Only process folders (not individual files)
        if (!folder.folder) {
            print("Skipping non-folder item: " + folder.name);
            return;
        }
        
        print("\nProcessing folder: " + folder.name);
        
        // Extract sample name from folder name
        // Assumes folder naming convention like: "SAMPLE_001_results" or "SAMPLE_001"
        var folderName = folder.name;
        
        // Method 1: Direct match (folder name is sample name)
        var sampleName = folderName;
        
        // Method 2: Extract from pattern (e.g., "SAMPLE_001_results" -> "SAMPLE_001")
        // Uncomment and adjust pattern as needed:
        // var match = folderName.match(/^(.+?)_results$/);
        // if (match) {
        //     sampleName = match[1];
        // }
        
        // Method 3: Remove suffix/prefix
        // sampleName = folderName.replace(/_results$/, "");
        
        print("  Looking for sample: " + sampleName);
        
        // Search for the sample
        var samples = searchSamples({ name: sampleName });
        
        if (samples && samples.length > 0) {
            var sample = samples[0];
            
            // Link the entire folder to the sample
            linkDataToSample({
                data: folder,
                sample: sample
            });
            
            print("  Successfully linked folder '" + folder.name + "' to sample '" + sample.name + "'");
            
            successCount++;
        } else {
            print("  Warning: No matching sample found for '" + sampleName + "'");
            failureCount++;
        }
    });
    
    // Summary
    print("\n=== Summary ===");
    print("Successfully linked: " + successCount + " folder(s)");
    print("Failed to link: " + failureCount + " folder(s)");
    print("Total processed: " + sampleFolders.length + " folder(s)");
}


function getSampleOutputFolders(input) {
var outputfolder = input.outputs["Output"][0];
print("outputfolder=" + outputfolder);
var firstLevelFilesAndFolders = searchDatas({
"parent": outputfolder,
"recursive": false
});

print("firstLevelFilesAndFolders=" + firstLevelFilesAndFolders);
var sampleOutputFolders = [];

firstLevelFilesAndFolders.forEach(function (firstLevelFileOrFolder) {
if (firstLevelFileOrFolder.folder == true && "reports" != firstLevelFileOrFolder.name) {
sampleOutputFolders.push(firstLevelFileOrFolder);
}
}
);

print("sampleOutputFolders=" + sampleOutputFolders);

return sampleOutputFolders;
}
```

{% endcode %}

## Best Practices <a href="#onfinishjavascript-bestpractices" id="onfinishjavascript-bestpractices"></a>

#### Error Handling <a href="#onfinishjavascript-1.errorhandling" id="onfinishjavascript-1.errorhandling"></a>

Check if results exist before processing:

{% code overflow="wrap" %}

```js
var samples = searchSamples({ name: sampleName });
if (samples && samples.length > 0) {
    // Process samples
} else {
    print("Warning: No samples found");
}
```

{% endcode %}

#### Logging <a href="#onfinishjavascript-2.logging" id="onfinishjavascript-2.logging"></a>

Use `print()` statements to provide visibility into your post-processing:

{% code overflow="wrap" %}

```js
print("Starting sample linking for " + bamFiles.length + " files");
```

{% endcode %}

#### Iterate Safely <a href="#onfinishjavascript-3.iteratesafely" id="onfinishjavascript-3.iteratesafely"></a>

Verify array contents before iteration:

{% code overflow="wrap" %}

```js
if (outputBams && outputBams.length > 0) {
    outputBams.forEach(function(bam) {
        // Process BAM file
    });
}
```

{% endcode %}

### Debugging <a href="#onfinishjavascript-debugging" id="onfinishjavascript-debugging"></a>

#### Viewing Logs <a href="#onfinishjavascript-viewinglogs" id="onfinishjavascript-viewinglogs"></a>

After the analysis completes, the onFinish execution logs are available in the analysis details screen to users with edit rights on the pipeline. All `print()` statements and error messages will appear in these logs.

### Advanced Use Cases <a href="#onfinishjavascript-advancedusecases" id="onfinishjavascript-advancedusecases"></a>

#### Conditional Linking Based on File Attributes <a href="#onfinishjavascript-conditionallinkingbasedonfileattributes" id="onfinishjavascript-conditionallinkingbasedonfileattributes"></a>

{% code overflow="wrap" %}

```js
// Only link BAM files larger than 1GB
var largeFiles = searchDatas({ nameRegex: ".*\\.bam$" })
    .filter(function(file) {
        return file.size > 1000000000; // 1GB in bytes
    });

print("Processing " + largeFiles.length + " large BAM files");
// ... continue with linking logic
```

{% endcode %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://help.ica.illumina.com/project/p-flow/f-pipelines/json-based-input-forms/onfinished.js.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
