Forms Tutorial 5
Tutorial 5: Deployment, Versioning, and Production Best Practices🔗
Objective🔗
Learn how to properly deploy, version, and maintain your forms in a production Open mSupply environment.
Form Versioning Strategy🔗
Understanding Version Numbers🔗
Open mSupply uses semantic versioning for forms:
MAJOR.MINOR.PATCH
2.6.1
│ │ │
│ │ └── Patch: Bug fixes, minor template adjustments
│ └──── Minor: New features, additional fields, enhanced functionality
└────── Major: Breaking changes, complete redesigns, different data structure
Directory Structure for Versioning🔗
standard_forms/
└── equipment-request/
├── latest/ # Always points to current version
│ ├── report-manifest.json
│ └── src/
├── 1.2.1/ # Previous version
│ ├── report-manifest.json
│ └── src/
├── 1.1.0/ # Older version
│ └── ...
└── 1.0.0/ # Original version
└── ...
Creating a New Version🔗
- Copy the current version:
# Copy latest to new version directory
cp -r equipment-request/latest equipment-request/1.3.0
# Update the latest version
# Make your changes in equipment-request/latest/
- Update the manifest version:
{
"is_custom": true,
"version": "1.3.0", // Update this
"code": "equipment-request",
"context": "REQUISITION",
"name": "Equipment Request",
"queries": {
"gql": "query.graphql"
},
"header": "header.html"
}
- Document changes:
Create a
CHANGELOG.mdin your form directory:
# Equipment Request Form Changelog
## [1.3.0] - 2024-01-15
### Added
- Priority indicators for high-quantity requests
- Supplier contact information section
- Cost estimation calculations
### Changed
- Improved table layout for better print formatting
- Enhanced error handling for missing data
### Fixed
- Date formatting issues in Safari
- Print page break problems
## [1.2.1] - 2023-12-10
### Fixed
- Missing item codes causing template errors
- CSS print media query specificity issues
## [1.2.0] - 2023-11-20
### Added
- Status banners for different requisition states
- Summary calculations section
- Conditional supplier information display
## [1.1.0] - 2023-10-15
### Added
- Signature sections for approval workflow
- Comments section display
- Line numbering in items table
## [1.0.0] - 2023-09-01
### Added
- Initial release of Equipment Request form
- Basic requisition display
- Item table with quantities
- Store header information
Production Deployment Checklist🔗
Pre-Deployment Testing🔗
- Data Validation Testing:
<!-- Create a test template for edge cases -->
<div class="test_scenarios">
<h2>Testing Scenarios</h2>
<!-- Test empty data -->
{% if data.requisition.lines.nodes | length == 0 %}
<p>✓ Empty requisition handling works</p>
{% endif %}
<!-- Test null values -->
{% if not data.requisition.otherParty %}
<p>✓ Missing supplier handling works</p>
{% endif %}
<!-- Test large datasets -->
{% if data.requisition.lines.nodes | length > 50 %}
<p>⚠ Large dataset - check performance</p>
{% endif %}
<!-- Test special characters -->
{% for line in data.requisition.lines.nodes %}
{% if '"' in line.item.name or '<' in line.item.name %}
<p>⚠ Special characters in item: {{ line.item.name }}</p>
{% endif %}
{% endfor %}
</div>
- Browser Compatibility Testing:
/* Add browser-specific CSS for compatibility */
@media print {
/* Webkit browsers (Chrome, Safari) */
@-webkit-keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
/* Firefox */
@-moz-document url-prefix() {
.signature_section {
display: block !important;
}
}
/* IE/Edge fallbacks */
@supports not (display: grid) {
.summary_grid {
display: table;
width: 100%;
}
.summary_item {
display: table-cell;
width: 33.33%;
}
}
}
- Performance Testing:
<!-- Add performance monitoring -->
<script>
// Only in development/testing
if (window.performance) {
window.addEventListener('load', function() {
setTimeout(function() {
const timing = performance.timing;
const loadTime = timing.loadEventEnd - timing.navigationStart;
console.log('Form load time:', loadTime + 'ms');
if (loadTime > 5000) {
console.warn('Form loading slowly - check query complexity');
}
}, 0);
});
}
</script>
Deployment Process🔗
- Backup Current Version:
# Create backup before deployment
cp -r standard_forms/equipment-request standard_forms/equipment-request.backup.$(date +%Y%m%d)
- Deploy to Staging:
# Copy to staging environment
rsync -av equipment-request/ staging-server:/path/to/forms/equipment-request/
- Production Deployment:
# Deploy to production (with rollback capability)
rsync -av equipment-request/ production-server:/path/to/forms/equipment-request/
- Verify Deployment:
# Check form is accessible
curl -I https://your-opensupply-instance/forms/equipment-request/latest/
# Verify manifest is valid JSON
cat /path/to/forms/equipment-request/latest/report-manifest.json | jq .
Configuration Management🔗
Environment-Specific Configurations🔗
Create environment-specific manifest files:
// report-manifest.production.json
{
"is_custom": true,
"version": "1.3.0",
"code": "equipment-request",
"context": "REQUISITION",
"name": "Equipment Request",
"queries": {
"gql": "query.graphql"
},
"arguments": {
"maxItems": 1000,
"includeDebug": false
},
"header": "header.html"
}
// report-manifest.development.json
{
"is_custom": true,
"version": "1.3.0-dev",
"code": "equipment-request",
"context": "REQUISITION",
"name": "Equipment Request (DEV)",
"queries": {
"gql": "query.graphql"
},
"arguments": {
"maxItems": 100,
"includeDebug": true
},
"header": "header.html"
}
Feature Flags in Templates🔗
<!-- Use feature flags for gradual rollouts -->
{% if arguments.includeDebug %}
<div class="debug_info">
<h3>Debug Information</h3>
<pre>{{ data | json_encode(pretty=true) }}</pre>
</div>
{% endif %}
{% if arguments.enableNewFeatures %}
<div class="new_features">
<!-- New experimental features -->
</div>
{% endif %}
Monitoring and Maintenance🔗
Error Tracking🔗
Add error tracking to your templates:
<!-- Error boundary for production -->
{% try %}
<!-- Main template content -->
{% include "main_content.html" %}
{% catch %}
<div class="error_fallback">
<h2>Form Temporarily Unavailable</h2>
<p>We're experiencing technical difficulties. Please try again later.</p>
<p>Error ID: {{ now() | date(format="%Y%m%d_%H%M%S") }}</p>
{% if arguments.includeDebug %}
<details>
<summary>Technical Details</summary>
<pre>{{ error }}</pre>
</details>
{% endif %}
</div>
{% endtry %}
Performance Monitoring🔗
<!-- Add performance markers -->
<script>
performance.mark('form-template-start');
// At end of template
performance.mark('form-template-end');
performance.measure('form-template-duration', 'form-template-start', 'form-template-end');
// Report if slow
const measures = performance.getEntriesByType('measure');
measures.forEach(measure => {
if (measure.duration > 1000) {
console.warn(`Slow template rendering: ${measure.duration}ms`);
}
});
</script>
Health Checks🔗
Create a health check template:
<!-- health-check.html -->
<!DOCTYPE html>
<html>
<head>
<title>Form Health Check</title>
</head>
<body>
<h1>Equipment Request Form Health Check</h1>
<div class="health_status">
{% if data.store %}
<p>✅ Store data: OK</p>
{% else %}
<p>❌ Store data: FAILED</p>
{% endif %}
{% if data.requisition %}
<p>✅ Requisition data: OK</p>
{% else %}
<p>❌ Requisition data: FAILED</p>
{% endif %}
<p>Timestamp: {{ now() | date(format="%Y-%m-%d %H:%M:%S") }}</p>
<p>Version: {{ manifest.version }}</p>
</div>
</body>
</html>
Security Best Practices🔗
Input Sanitization🔗
<!-- Always escape user-generated content -->
<div class="comments">
<h3>Comments</h3>
<!-- Good: Automatic escaping -->
<p>{{ data.requisition.comment }}</p>
<!-- Avoid: Raw output (only if absolutely necessary and data is trusted) -->
<!-- {{ data.requisition.comment | safe }} -->
</div>
<!-- Sanitize data in macros -->
{% macro safeDisplay(value, maxLength=100) %}
{% if value %}
{{ value | truncate(length=maxLength) | escape }}
{% else %}
N/A
{% endif %}
{% endmacro %}
Access Control🔗
Ensure your queries respect access controls:
query SecureRequisitionQuery($storeId: String!, $dataId: String!) {
# Query automatically respects user permissions
requisition(storeId: $storeId, id: $dataId) {
... on RequisitionNode {
# Only returns data user has access to
id
status
requisitionNumber
# This will be filtered based on user's store access
otherParty(storeId: $storeId) {
name
code
}
}
}
}
Backup and Recovery🔗
Automated Backups🔗
#!/bin/bash
# backup-forms.sh
BACKUP_DIR="/backups/forms/$(date +%Y-%m-%d)"
FORMS_DIR="/path/to/standard_forms"
mkdir -p "$BACKUP_DIR"
# Create timestamped backup
tar -czf "$BACKUP_DIR/forms-backup-$(date +%H%M%S).tar.gz" -C "$FORMS_DIR" .
# Keep only last 30 days of backups
find /backups/forms/ -type d -mtime +30 -exec rm -rf {} \;
echo "Backup completed: $BACKUP_DIR"
Recovery Procedures🔗
#!/bin/bash
# restore-form.sh
FORM_NAME="$1"
BACKUP_DATE="$2"
if [ -z "$FORM_NAME" ] || [ -z "$BACKUP_DATE" ]; then
echo "Usage: $0 <form-name> <backup-date>"
echo "Example: $0 equipment-request 2024-01-15"
exit 1
fi
BACKUP_FILE="/backups/forms/$BACKUP_DATE/forms-backup-*.tar.gz"
RESTORE_DIR="/path/to/standard_forms"
# Create rollback point
cp -r "$RESTORE_DIR/$FORM_NAME" "$RESTORE_DIR/$FORM_NAME.rollback.$(date +%Y%m%d_%H%M%S)"
# Extract specific form from backup
tar -xzf $BACKUP_FILE -C "/tmp" "$FORM_NAME"
cp -r "/tmp/$FORM_NAME" "$RESTORE_DIR/"
echo "Form $FORM_NAME restored from backup $BACKUP_DATE"
Documentation Standards🔗
Form Documentation Template🔗
Create a README.md for each form:
# Equipment Request Form
## Overview
Custom form for equipment requisitions with enhanced features for priority handling and cost estimation.
## Version History
- **1.3.0** (Current) - Added priority indicators and cost estimation
- **1.2.1** - Bug fixes for date formatting
- **1.2.0** - Added status banners and calculations
- **1.1.0** - Added signatures and comments
- **1.0.0** - Initial release
## Dependencies
- Context: REQUISITION
- Minimum Open mSupply version: 2.5.0
- Required permissions: requisition.read
## Configuration
- `maxItems`: Maximum number of items to display (default: 100)
- `includeStats`: Include stock statistics (default: true)
- `includeDebug`: Show debug information (default: false)
## Data Requirements
### Required Fields
- `requisition.id`
- `requisition.requisitionNumber`
- `requisition.status`
### Optional Fields
- `requisition.comment`
- `requisition.otherParty`
- `itemStats` (if includeStats is true)
## Known Issues
- Print layout may have issues in Firefox < 90
- Large datasets (>1000 items) may cause performance issues
- Special characters in item names require escaping
## Testing
Run the health check template to verify form functionality:
GET /forms/equipment-request/health-check
## Support
- Documentation: [Internal Wiki Link]
- Issue Tracker: [Ticket System Link]
- Contact: dev-team@yourorganization.com
Troubleshooting Guide🔗
Common Issues and Solutions🔗
- Template Rendering Errors:
<!-- Add comprehensive error handling -->
{% if not data %}
<div class="error">No data available - check query and permissions</div>
{% elif data.requisition.__typename == "NodeError" %}
<div class="error">{{ data.requisition.error.description }}</div>
{% elif not data.requisition %}
<div class="error">Requisition not found - check ID parameter</div>
{% else %}
<!-- Normal template content -->
{% endif %}
- Performance Issues:
# Optimize queries by limiting data
query OptimizedQuery($storeId: String!, $dataId: String!) {
requisition(storeId: $storeId, id: $dataId) {
... on RequisitionNode {
# Only essential fields
id
status
requisitionNumber
# Limit lines if necessary
lines(first: 500) {
nodes {
requestedQuantity
item {
code
name
}
}
}
}
}
}
- Print Layout Issues:
/* Comprehensive print CSS */
@media print {
/* Force page breaks */
.page_break {
page-break-before: always;
}
/* Avoid breaks inside elements */
.item_row {
page-break-inside: avoid;
}
/* Hide screen-only elements */
.no_print {
display: none !important;
}
/* Ensure black text for printing */
* {
color: black !important;
background: white !important;
}
/* Show print-only elements */
.print_only {
display: block !important;
}
}
What We've Learned🔗
- Proper versioning strategies and directory structure
- Production deployment checklists and processes
- Environment-specific configurations and feature flags
- Monitoring, error tracking, and performance optimization
- Security best practices and access control
- Backup and recovery procedures
- Documentation standards and troubleshooting guides
Next Steps🔗
With these five tutorials, you now have a comprehensive understanding of:
- Form Architecture - Understanding the components and structure
- Basic Development - Creating your first form with manifest and templates
- Advanced Templating - Using complex Tera features and data manipulation
- GraphQL Mastery - Writing efficient queries and handling data
- Production Deployment - Managing versions, deployment, and maintenance
You're now ready to create sophisticated, production-ready forms for Open mSupply. Remember to:
- Start simple and iterate
- Test thoroughly across different scenarios
- Document your forms well
- Monitor performance in production
- Keep security and access control in mind
- Maintain proper version control
Happy form development!