diff --git a/.eslintrc.js b/.eslintrc.js
index db665ad..4313e0e 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -13,4 +13,4 @@ module.exports = {
'no-console': 'warn',
'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
},
-};
\ No newline at end of file
+};
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 4085987..543adcb 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -2,9 +2,9 @@ name: Test
on:
push:
- branches: [ master ]
+ branches: [master]
pull_request:
- branches: [ master ]
+ branches: [master]
jobs:
test:
@@ -13,99 +13,99 @@ jobs:
matrix:
node-version: [20.x]
steps:
- - uses: actions/checkout@v4
- - name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v4
- with:
- node-version: ${{ matrix.node-version }}
- cache: 'npm'
- - name: Install dependencies
- run: |
- sudo apt-get update
- sudo apt-get install -y libgbm-dev jq xvfb
- npm ci
+ - uses: actions/checkout@v4
+ - name: Use Node.js ${{ matrix.node-version }}
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ matrix.node-version }}
+ cache: 'npm'
+ - name: Install dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y libgbm-dev jq xvfb
+ npm ci
- - name: Lint
- run: |
- npm run lint
- npm run format -- --check
+ - name: Lint
+ run: |
+ npm run lint
+ npm run format -- --check
- - name: Install Chrome
- uses: browser-actions/setup-chrome@latest
- with:
- chrome-version: stable
+ - name: Install Chrome
+ uses: browser-actions/setup-chrome@latest
+ with:
+ chrome-version: stable
- - name: Run tests with coverage
- run: |
- # Run tests with coverage and capture results in JSON format
- npm test -- --coverage-reporters=lcov --coverage-reporters=json-summary --json --outputFile=test-results.json
+ - name: Run tests with coverage
+ run: |
+ # Run tests with coverage and capture results in JSON format
+ npm test -- --coverage-reporters=lcov --coverage-reporters=json-summary --json --outputFile=test-results.json
- # Extract coverage from JSON and format for summary
- echo "# Test Results (Node ${{ matrix.node-version }})" >> $GITHUB_STEP_SUMMARY
- echo "## Coverage" >> $GITHUB_STEP_SUMMARY
- echo "| Type | Coverage | Details |" >> $GITHUB_STEP_SUMMARY
- echo "|------|----------|----------|" >> $GITHUB_STEP_SUMMARY
+ # Extract coverage from JSON and format for summary
+ echo "# Test Results (Node ${{ matrix.node-version }})" >> $GITHUB_STEP_SUMMARY
+ echo "## Coverage" >> $GITHUB_STEP_SUMMARY
+ echo "| Type | Coverage | Details |" >> $GITHUB_STEP_SUMMARY
+ echo "|------|----------|----------|" >> $GITHUB_STEP_SUMMARY
- # Use jq to parse the coverage JSON
- jq -r '.total |
- "| Statements | \(.statements.pct)% | \(.statements.covered)/\(.statements.total) |\n| Branches | \(.branches.pct)% | \(.branches.covered)/\(.branches.total) |\n| Functions | \(.functions.pct)% | \(.functions.covered)/\(.functions.total) |\n| Lines | \(.lines.pct)% | \(.lines.covered)/\(.lines.total) |"' coverage/coverage-summary.json >> $GITHUB_STEP_SUMMARY || {
- echo "| Coverage data not available | - | - |" >> $GITHUB_STEP_SUMMARY
- }
+ # Use jq to parse the coverage JSON
+ jq -r '.total |
+ "| Statements | \(.statements.pct)% | \(.statements.covered)/\(.statements.total) |\n| Branches | \(.branches.pct)% | \(.branches.covered)/\(.branches.total) |\n| Functions | \(.functions.pct)% | \(.functions.covered)/\(.functions.total) |\n| Lines | \(.lines.pct)% | \(.lines.covered)/\(.lines.total) |"' coverage/coverage-summary.json >> $GITHUB_STEP_SUMMARY || {
+ echo "| Coverage data not available | - | - |" >> $GITHUB_STEP_SUMMARY
+ }
- # Add test results using jq
- echo "## Test Results" >> $GITHUB_STEP_SUMMARY
- echo "| Total | Passed | Failed | Skipped |" >> $GITHUB_STEP_SUMMARY
- echo "|-------|---------|---------|----------|" >> $GITHUB_STEP_SUMMARY
- jq -r '"| \(.numTotalTests) | \(.numPassedTests) | \(.numFailedTests) | \(.numPendingTests) |"' test-results.json >> $GITHUB_STEP_SUMMARY
+ # Add test results using jq
+ echo "## Test Results" >> $GITHUB_STEP_SUMMARY
+ echo "| Total | Passed | Failed | Skipped |" >> $GITHUB_STEP_SUMMARY
+ echo "|-------|---------|---------|----------|" >> $GITHUB_STEP_SUMMARY
+ jq -r '"| \(.numTotalTests) | \(.numPassedTests) | \(.numFailedTests) | \(.numPendingTests) |"' test-results.json >> $GITHUB_STEP_SUMMARY
- - name: Upload coverage report
- uses: actions/upload-artifact@v4
- with:
- name: coverage-report
- path: coverage/
+ - name: Upload coverage report
+ uses: actions/upload-artifact@v4
+ with:
+ name: coverage-report
+ path: coverage/
- - name: Run visual tests
- run: |
- mkdir -p __image_snapshots__
- CHROME_PATH="$(which chrome)" xvfb-run --auto-servernum --server-args="-screen 0 1280x800x24" npm run test:visual:ci -- -u --json --outputFile=visual-test-results.json
+ - name: Run visual tests
+ run: |
+ mkdir -p __image_snapshots__
+ CHROME_PATH="$(which chrome)" xvfb-run --auto-servernum --server-args="-screen 0 1280x800x24" npm run test:visual:ci -- -u --json --outputFile=visual-test-results.json
- # Show test results
- if [ -f "visual-test-results.json" ]; then
- echo "## Visual Test Results" >> $GITHUB_STEP_SUMMARY
- echo "| Total | Passed | Failed | Skipped |" >> $GITHUB_STEP_SUMMARY
- echo "|-------|---------|---------|----------|" >> $GITHUB_STEP_SUMMARY
- jq -r '"| \(.numTotalTests) | \(.numPassedTests) | \(.numFailedTests) | \(.numPendingTests) |"' visual-test-results.json >> $GITHUB_STEP_SUMMARY
- echo "" >> $GITHUB_STEP_SUMMARY
- fi
+ # Show test results
+ if [ -f "visual-test-results.json" ]; then
+ echo "## Visual Test Results" >> $GITHUB_STEP_SUMMARY
+ echo "| Total | Passed | Failed | Skipped |" >> $GITHUB_STEP_SUMMARY
+ echo "|-------|---------|---------|----------|" >> $GITHUB_STEP_SUMMARY
+ jq -r '"| \(.numTotalTests) | \(.numPassedTests) | \(.numFailedTests) | \(.numPendingTests) |"' visual-test-results.json >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ fi
- # Show screenshots in summary
- {
- echo "## Screenshots"
- echo ""
- for img in __image_snapshots__/ci-*.png; do
- if [ -f "$img" ]; then
- name=$(basename "$img" | sed -E 's/ci-Zapier Visual Tests (.+)-1\.png/\1/')
- echo "### $name"
- echo ""
- echo ""
- echo "View Screenshot
"
- echo ""
- encoded=$(cat "$img" | base64)
- echo ""
- echo ""
- echo " "
- echo ""
- fi
- done
- } >> $GITHUB_STEP_SUMMARY
+ # Show screenshots in summary
+ {
+ echo "## Screenshots"
+ echo ""
+ for img in __image_snapshots__/ci-*.png; do
+ if [ -f "$img" ]; then
+ name=$(basename "$img" | sed -E 's/ci-Zapier Visual Tests (.+)-1\.png/\1/')
+ echo "### $name"
+ echo ""
+ echo ""
+ echo "View Screenshot
"
+ echo ""
+ encoded=$(cat "$img" | base64)
+ echo ""
+ echo ""
+ echo " "
+ echo ""
+ fi
+ done
+ } >> $GITHUB_STEP_SUMMARY
- # Create a flat directory for artifacts
- mkdir -p artifacts
- cp __image_snapshots__/*.png artifacts/
+ # Create a flat directory for artifacts
+ mkdir -p artifacts
+ cp __image_snapshots__/*.png artifacts/
- - name: Upload artifacts
- if: always()
- uses: actions/upload-artifact@v4
- with:
- name: screenshots
- path: artifacts/
\ No newline at end of file
+ - name: Upload artifacts
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: screenshots
+ path: artifacts/
diff --git a/.github/workflows/zapier-release.yml b/.github/workflows/zapier-release.yml
index 807a5db..5f4d54a 100644
--- a/.github/workflows/zapier-release.yml
+++ b/.github/workflows/zapier-release.yml
@@ -3,7 +3,7 @@ name: Zapier Release
on:
push:
tags:
- - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
+ - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
jobs:
deploy:
@@ -29,4 +29,4 @@ jobs:
- name: Deploy to Zapier
env:
ZAPIER_DEPLOY_KEY: ${{ secrets.ZAPIER_DEPLOY_KEY }}
- run: zapier push --skip-npm-install
\ No newline at end of file
+ run: zapier push --skip-npm-install
diff --git a/.markdownlint.json b/.markdownlint.json
index 73cb980..67d2ae5 100644
--- a/.markdownlint.json
+++ b/.markdownlint.json
@@ -1,3 +1,3 @@
{
"MD013": false
-}
\ No newline at end of file
+}
diff --git a/.prettierrc b/.prettierrc
index 860c5a7..a95cb05 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -4,4 +4,4 @@
"trailingComma": "es5",
"printWidth": 100,
"tabWidth": 2
-}
\ No newline at end of file
+}
diff --git a/README.md b/README.md
index fb7cc6c..11ba21e 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,6 @@
# Screenly Zapier Integration
-[![Test](
- https://github.com/screenly/zapier/actions/workflows/test.yml/badge.svg?branch=master
-)](
- https://github.com/screenly/zapier/actions/workflows/test.yml
-)
+[![Test](https://github.com/screenly/zapier/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/screenly/zapier/actions/workflows/test.yml)
A Zapier integration for automating Screenly digital signage operations. Connect your
Screenly displays with 5000+ apps.
@@ -13,28 +9,26 @@ Screenly displays with 5000+ apps.
1. **Install the Integration**
- * Go to [Zapier's Screenly Integration](
- https://zapier.com/apps/screenly/integrations
- ) page
- * Click "Connect Screenly"
- * When prompted, enter your Screenly API key from
+ - Go to [Zapier's Screenly Integration](https://zapier.com/apps/screenly/integrations) page
+ - Click "Connect Screenly"
+ - When prompted, enter your Screenly API key from
[Screenly Settings](https://app.screenlyapp.com/settings/api-keys)
2. **Create Your First Zap**
- * Click "Make a Zap"
- * Choose your trigger app (e.g., Dropbox, SharePoint, Google Drive)
- * Choose Screenly as your action app
- * Select one of these actions:
- * Upload Asset - Add new content to your Screenly library
- * Add to Playlist - Add content to a specific playlist
- * Complete Workflow - Upload, add to playlist, and assign to screens
+ - Click "Make a Zap"
+ - Choose your trigger app (e.g., Dropbox, SharePoint, Google Drive)
+ - Choose Screenly as your action app
+ - Select one of these actions:
+ - Upload Asset - Add new content to your Screenly library
+ - Add to Playlist - Add content to a specific playlist
+ - Complete Workflow - Upload, add to playlist, and assign to screens
3. **Example: Auto-Upload from Dropbox**
- * Trigger: New file in Dropbox folder
- * Action: Screenly - Upload Asset
- * Test your Zap and turn it on
+ - Trigger: New file in Dropbox folder
+ - Action: Screenly - Upload Asset
+ - Test your Zap and turn it on
## Available Actions
@@ -42,112 +36,116 @@ Screenly displays with 5000+ apps.
Upload files to your Screenly account:
-* Supports images, videos, and URLs
-* Set custom titles and durations
-* Accepts files via URL or direct upload
+- Supports images, videos, and URLs
+- Set custom titles and durations
+- Accepts files via URL or direct upload
### 2. Add to Playlist
Add content to your playlists:
-* Choose from your existing playlists
-* Set display duration
-* Schedule content with start/end dates
-* Select target screens
+- Choose from your existing playlists
+- Set display duration
+- Schedule content with start/end dates
+- Select target screens
### 3. Complete Workflow
Do everything in one step:
-* Upload new content
-* Add to playlist
-* Assign to screens
-* Set schedule
-* Perfect for automated content management
+- Upload new content
+- Add to playlist
+- Assign to screens
+- Set schedule
+- Perfect for automated content management
### 4. Cleanup
Maintain your content library:
-* Remove Zapier-created content
-* Automatic cleanup based on rules
-* Safe deletion with confirmation
+- Remove Zapier-created content
+- Automatic cleanup based on rules
+- Safe deletion with confirmation
### 5. Enable/Disable Playlist
Control playlist visibility dynamically:
-* Enable or disable playlists based on conditions
-* Schedule playlist activation periods
-* Perfect for:
- * Weather-based content
- * Time-sensitive promotions
- * Event-driven displays
- * Seasonal campaigns
+- Enable or disable playlists based on conditions
+- Schedule playlist activation periods
+- Perfect for:
+ - Weather-based content
+ - Time-sensitive promotions
+ - Event-driven displays
+ - Seasonal campaigns
## Common Use Cases
1. **Automated Content Updates**
- * Connect Dropbox/Google Drive/SharePoint
- * New files automatically appear on screens
- * Perfect for marketing teams
+
+ - Connect Dropbox/Google Drive/SharePoint
+ - New files automatically appear on screens
+ - Perfect for marketing teams
2. **Scheduled Displays**
- * Show content during specific times
- * Automate weekly/monthly updates
- * Event-based content management
+
+ - Show content during specific times
+ - Automate weekly/monthly updates
+ - Event-based content management
3. **Multi-Screen Management**
- * Different content for different locations
- * Department-specific displays
- * Emergency broadcast system
+
+ - Different content for different locations
+ - Department-specific displays
+ - Emergency broadcast system
4. **Content Library Management**
- * Automatic file organization
- * Scheduled content rotation
- * Cleanup old content
+
+ - Automatic file organization
+ - Scheduled content rotation
+ - Cleanup old content
5. **Weather-Based Dynamic Content**
- * **Example: Smart Weather-Driven Displays**
- * Use Weather API as trigger (hourly check)
- * **Temperature-Based Content:**
- * When temperature > 20°C:
- * Enable "Summer Treats" playlist (ice cream, cold drinks)
- * Disable "Winter Warmers" playlist
- * When temperature < 0°C:
- * Enable "Winter Warmers" playlist (hot chocolate, soups)
- * Disable "Summer Treats" playlist
- * **Rain-Based Content:**
- * When it's raining:
- * Enable "Rainy Day Specials" playlist (umbrellas, raincoats)
- * Show "Stay Dry" promotions
- * When rain stops:
- * Return to default seasonal playlist
- * Disable rain-specific promotions
- * **Setup Steps:**
+ - **Example: Smart Weather-Driven Displays**
+ - Use Weather API as trigger (hourly check)
+ - **Temperature-Based Content:**
+ - When temperature > 20°C:
+ - Enable "Summer Treats" playlist (ice cream, cold drinks)
+ - Disable "Winter Warmers" playlist
+ - When temperature < 0°C:
+ - Enable "Winter Warmers" playlist (hot chocolate, soups)
+ - Disable "Summer Treats" playlist
+ - **Rain-Based Content:**
+ - When it's raining:
+ - Enable "Rainy Day Specials" playlist (umbrellas, raincoats)
+ - Show "Stay Dry" promotions
+ - When rain stops:
+ - Return to default seasonal playlist
+ - Disable rain-specific promotions
+ - **Setup Steps:**
1. Create weather-specific playlists in Screenly:
- * "Summer Treats" - cold items
- * "Winter Warmers" - hot items
- * "Rainy Day Specials" - weather protection items
+ - "Summer Treats" - cold items
+ - "Winter Warmers" - hot items
+ - "Rainy Day Specials" - weather protection items
2. In Zapier:
- * Trigger: Weather by Zapier (check every hour)
- * Filter 1: Temperature conditions
- * Filter 2: Precipitation conditions
- * Actions:
- * Enable/Disable appropriate playlists
- * Update content based on current conditions
- * **Benefits:**
- * Automatically adapt to multiple weather conditions
- * Increase sales with contextual promotions
- * Show relevant products at the right time
- * Create urgency with weather-specific offers
- * No manual playlist management needed
+ - Trigger: Weather by Zapier (check every hour)
+ - Filter 1: Temperature conditions
+ - Filter 2: Precipitation conditions
+ - Actions:
+ - Enable/Disable appropriate playlists
+ - Update content based on current conditions
+ - **Benefits:**
+ - Automatically adapt to multiple weather conditions
+ - Increase sales with contextual promotions
+ - Show relevant products at the right time
+ - Create urgency with weather-specific offers
+ - No manual playlist management needed
## Security
-* API keys are stored securely by Zapier
-* All API requests are made over HTTPS
-* The integration follows Zapier's security best practices
+- API keys are stored securely by Zapier
+- All API requests are made over HTTPS
+- The integration follows Zapier's security best practices
## Example Integrations
@@ -156,60 +154,66 @@ Control playlist visibility dynamically:
#### Dropbox
1. **Simple Asset Upload**
- * Trigger: New file in Dropbox folder
- * Action: Upload to Screenly
- * Use Case: Quickly add new content to your Screenly library
+
+ - Trigger: New file in Dropbox folder
+ - Action: Upload to Screenly
+ - Use Case: Quickly add new content to your Screenly library
2. **Automated Playlist Management**
- * Trigger: New file in specific Dropbox folder
- * Action: Complete Workflow (Upload + Playlist + Screen)
- * Use Case: Different folders map to different screens/playlists
+ - Trigger: New file in specific Dropbox folder
+ - Action: Complete Workflow (Upload + Playlist + Screen)
+ - Use Case: Different folders map to different screens/playlists
#### Box
1. **Content Library Management**
- * Trigger: New file in Box folder
- * Action: Upload to Screenly with metadata
- * Use Case: Use Box metadata for asset scheduling
+
+ - Trigger: New file in Box folder
+ - Action: Upload to Screenly with metadata
+ - Use Case: Use Box metadata for asset scheduling
2. **Multi-Screen Campaign**
- * Trigger: New file with specific tag in Box
- * Action: Complete Workflow
- * Use Case: Marketing campaigns across multiple screens
+ - Trigger: New file with specific tag in Box
+ - Action: Complete Workflow
+ - Use Case: Marketing campaigns across multiple screens
#### SharePoint
1. **Corporate Communications**
- * Trigger: New file in SharePoint document library
- * Action: Complete Workflow
- * Use Case: Display company announcements on office screens
+
+ - Trigger: New file in SharePoint document library
+ - Action: Complete Workflow
+ - Use Case: Display company announcements on office screens
2. **Department-Specific Content**
- * Trigger: New file in department SharePoint folder
- * Action: Upload + Add to Department Playlist
- * Use Case: Each department manages their own digital signage content
+
+ - Trigger: New file in department SharePoint folder
+ - Action: Upload + Add to Department Playlist
+ - Use Case: Each department manages their own digital signage content
3. **Event Display Management**
- * Trigger: New item in SharePoint Events list
- * Action: Complete Workflow with scheduling
- * Use Case: Automatically display event information during relevant times
+
+ - Trigger: New item in SharePoint Events list
+ - Action: Complete Workflow with scheduling
+ - Use Case: Automatically display event information during relevant times
4. **Multi-Location Content Distribution**
- * Trigger: New file in SharePoint with location metadata
- * Action: Complete Workflow with screen selection
- * Use Case: Distribute content to specific office locations based on SharePoint metadata
+ - Trigger: New file in SharePoint with location metadata
+ - Action: Complete Workflow with screen selection
+ - Use Case: Distribute content to specific office locations based on SharePoint metadata
### Automated Maintenance
1. **Content Cleanup**
- * Trigger: Schedule (e.g., weekly)
- * Action: Cleanup Zapier Content
- * Use Case: Maintain a clean asset library by removing old content
+
+ - Trigger: Schedule (e.g., weekly)
+ - Action: Cleanup Zapier Content
+ - Use Case: Maintain a clean asset library by removing old content
2. **Content Rotation**
- * Trigger: Schedule or SharePoint/Dropbox/Box update
- * Action: Complete Workflow with end dates
- * Use Case: Automatically rotate content based on schedules
+ - Trigger: Schedule or SharePoint/Dropbox/Box update
+ - Action: Complete Workflow with end dates
+ - Use Case: Automatically rotate content based on schedules
---
@@ -217,9 +221,9 @@ Control playlist visibility dynamically:
### Prerequisites
-* Node.js 20.x
-* npm
-* A Screenly account with API access
+- Node.js 20.x
+- npm
+- A Screenly account with API access
### Installation
@@ -246,33 +250,33 @@ Control playlist visibility dynamically:
```
3. Configure your Screenly API key:
- * Get your API key from [Screenly Settings](https://app.screenlyapp.com/settings/api-keys)
- * When setting up the Zapier integration, you'll be prompted to enter this API key
+ - Get your API key from [Screenly Settings](https://app.screenlyapp.com/settings/api-keys)
+ - When setting up the Zapier integration, you'll be prompted to enter this API key
### Available Scripts
-* `npm test` - Run unit tests with coverage
-* `npm run test:watch` - Run tests in watch mode
-* `npm run lint` - Run ESLint
-* `npm run format` - Format code with Prettier
-* `npm run clean` - Clean up generated files
-* `npm run prepare` - Install git hooks (runs automatically after npm install)
+- `npm test` - Run unit tests with coverage
+- `npm run test:watch` - Run tests in watch mode
+- `npm run lint` - Run ESLint
+- `npm run format` - Format code with Prettier
+- `npm run clean` - Clean up generated files
+- `npm run prepare` - Install git hooks (runs automatically after npm install)
Visual tests are only run in CI environment:
-* `npm run test:visual` - Displays information about visual tests
-* `npm run test:visual:ci` - Runs visual tests (CI only)
+- `npm run test:visual` - Displays information about visual tests
+- `npm run test:visual:ci` - Runs visual tests (CI only)
### Code Quality
The project uses several tools to ensure code quality:
-* **ESLint** - For code linting
-* **Prettier** - For code formatting
-* **Jest** - For testing
-* **Husky** - For git hooks
-* **lint-staged** - For running checks on staged files
-* **markdownlint** - For markdown formatting
+- **ESLint** - For code linting
+- **Prettier** - For code formatting
+- **Jest** - For testing
+- **Husky** - For git hooks
+- **lint-staged** - For running checks on staged files
+- **markdownlint** - For markdown formatting
These run automatically on commit, but you can also run them manually:
@@ -285,27 +289,27 @@ npm run format # Fix code formatting
Pre-commit hooks are set up to:
-* Run unit tests
-* Lint JavaScript files
-* Format code with Prettier
-* Check markdown formatting
-* Run tests
+- Run unit tests
+- Lint JavaScript files
+- Format code with Prettier
+- Check markdown formatting
+- Run tests
The hooks are configured in:
-* `.husky/pre-commit` - Hook scripts
-* `package.json` - lint-staged configuration
-* `.eslintrc.js` - ESLint rules
-* `.prettierrc` - Prettier configuration
-* `.markdownlint.json` - Markdown linting rules
+- `.husky/pre-commit` - Hook scripts
+- `package.json` - lint-staged configuration
+- `.eslintrc.js` - ESLint rules
+- `.prettierrc` - Prettier configuration
+- `.markdownlint.json` - Markdown linting rules
### Visual Testing
Visual tests are automatically run in CI and generate screenshots of:
-* Upload Asset Form
-* Complete Workflow Form
-* Cleanup Confirmation Form
+- Upload Asset Form
+- Complete Workflow Form
+- Cleanup Confirmation Form
These tests are skipped locally to avoid environment-specific issues.
@@ -313,8 +317,8 @@ These tests are skipped locally to avoid environment-specific issues.
1. **Dependency Management**
- * Never edit package-lock.json manually
- * Use npm commands to manage dependencies:
+ - Never edit package-lock.json manually
+ - Use npm commands to manage dependencies:
```bash
npm install # Add dependency
@@ -324,37 +328,41 @@ These tests are skipped locally to avoid environment-specific issues.
```
2. **Code Style**
- * ESLint and Prettier are configured
- * Formatting is automatically handled on commit
- * Run `npm run format` to manually format code
+
+ - ESLint and Prettier are configured
+ - Formatting is automatically handled on commit
+ - Run `npm run format` to manually format code
3. **Testing**
- * Maintain test coverage above 80%
- * Write tests for new features
- * Visual tests are CI-only
+
+ - Maintain test coverage above 80%
+ - Write tests for new features
+ - Visual tests are CI-only
4. **Git Workflow**
- * Commits are automatically linted
- * Visual tests run on pull requests
- * CI checks must pass before merge
+ - Commits are automatically linted
+ - Visual tests run on pull requests
+ - CI checks must pass before merge
### Deployment
The integration is automatically deployed to Zapier when a new version tag is pushed to GitHub.
1. Create and push a new version tag:
+
```bash
git tag -a v0.1.0 -m "Initial release"
git push origin v0.1.0
```
2. The GitHub Action will:
- * Run tests
- * Deploy to Zapier
+ - Run tests
+ - Deploy to Zapier
### Setting up Zapier Deployment
1. Get your Zapier Deploy Key:
+
```bash
zapier login
zapier register "Screenly" # Only needed for first-time setup
@@ -362,15 +370,15 @@ The integration is automatically deployed to Zapier when a new version tag is pu
```
2. Add the deploy key to GitHub:
- * Go to your repository's Settings > Secrets > Actions
- * Add a new secret named `ZAPIER_DEPLOY_KEY`
- * Paste your deploy key as the value
+ - Go to your repository's Settings > Secrets > Actions
+ - Add a new secret named `ZAPIER_DEPLOY_KEY`
+ - Paste your deploy key as the value
### Version Management
-* Use semantic versioning (MAJOR.MINOR.PATCH)
-* Tag format: `v*.*.*` (e.g., v0.1.0, v1.0.0)
-* Pre-release versions: Use `-beta`, `-alpha` suffixes
+- Use semantic versioning (MAJOR.MINOR.PATCH)
+- Tag format: `v*.*.*` (e.g., v0.1.0, v1.0.0)
+- Pre-release versions: Use `-beta`, `-alpha` suffixes
### License
diff --git a/index.js b/index.js
index e70eb0a..25f7574 100644
--- a/index.js
+++ b/index.js
@@ -6,10 +6,10 @@ const authentication = {
type: 'custom',
test: {
headers: {
- Authorization: 'Token {{bundle.authData.api_key}}'
+ Authorization: 'Token {{bundle.authData.api_key}}',
},
url: 'https://api.screenlyapp.com/api/v4/assets/',
- method: 'GET'
+ method: 'GET',
},
fields: [
{
@@ -17,10 +17,10 @@ const authentication = {
type: 'string',
required: true,
label: 'API Key',
- helpText: 'Your Screenly API key from https://app.screenlyapp.com/settings/api-keys'
- }
+ helpText: 'Your Screenly API key from https://app.screenlyapp.com/settings/api-keys',
+ },
],
- connectionLabel: '{{bundle.authData.api_key}}'
+ connectionLabel: '{{bundle.authData.api_key}}',
};
// Upload Asset Action
@@ -29,7 +29,7 @@ const uploadAsset = {
noun: 'Asset',
display: {
label: 'Upload Asset',
- description: 'Upload a new asset to Screenly'
+ description: 'Upload a new asset to Screenly',
},
operation: {
inputFields: [
@@ -38,14 +38,14 @@ const uploadAsset = {
label: 'File URL',
type: 'string',
required: true,
- helpText: 'The URL of the file to upload'
+ helpText: 'The URL of the file to upload',
},
{
key: 'title',
label: 'Title',
type: 'string',
required: true,
- helpText: 'Title of the asset'
+ helpText: 'Title of the asset',
},
{
key: 'duration',
@@ -53,14 +53,14 @@ const uploadAsset = {
type: 'integer',
required: false,
default: '10',
- helpText: 'How long should the asset be shown (in seconds)'
- }
+ helpText: 'How long should the asset be shown (in seconds)',
+ },
],
perform: async (z, bundle) => {
// First, fetch the file
const fileResponse = await z.request({
url: bundle.inputData.file,
- raw: true
+ raw: true,
});
if (fileResponse.status >= 400) {
@@ -77,9 +77,9 @@ const uploadAsset = {
url: 'https://api.screenlyapp.com/api/v4/assets/',
method: 'POST',
headers: {
- Authorization: `Token ${bundle.authData.api_key}`
+ Authorization: `Token ${bundle.authData.api_key}`,
},
- body: formData
+ body: formData,
});
return utils.handleError(response, 'Failed to upload asset');
@@ -89,9 +89,9 @@ const uploadAsset = {
title: 'Sample Asset',
duration: 10,
type: 'image',
- url: 'https://example.com/sample.jpg'
- }
- }
+ url: 'https://example.com/sample.jpg',
+ },
+ },
};
// Schedule Playlist Item Action
@@ -100,7 +100,7 @@ const schedulePlaylistItem = {
noun: 'Playlist Item',
display: {
label: 'Add Asset to Playlist',
- description: 'Add an asset to a playlist with scheduling'
+ description: 'Add an asset to a playlist with scheduling',
},
operation: {
inputFields: [
@@ -110,7 +110,7 @@ const schedulePlaylistItem = {
type: 'string',
required: true,
dynamic: 'get_playlists.id.name',
- helpText: 'Select the playlist'
+ helpText: 'Select the playlist',
},
{
key: 'asset_id',
@@ -118,7 +118,7 @@ const schedulePlaylistItem = {
type: 'string',
required: true,
dynamic: 'get_assets.id.title',
- helpText: 'Select the asset to schedule'
+ helpText: 'Select the asset to schedule',
},
{
key: 'duration',
@@ -126,22 +126,22 @@ const schedulePlaylistItem = {
type: 'integer',
required: false,
default: '10',
- helpText: 'How long should this asset be shown (in seconds)'
+ helpText: 'How long should this asset be shown (in seconds)',
},
{
key: 'start_date',
label: 'Start Date',
type: 'datetime',
required: false,
- helpText: 'When should this item start being available for playback (optional)'
+ helpText: 'When should this item start being available for playback (optional)',
},
{
key: 'end_date',
label: 'End Date',
type: 'datetime',
required: false,
- helpText: 'When should this item stop being available (optional)'
- }
+ helpText: 'When should this item stop being available (optional)',
+ },
],
perform: async (z, bundle) => {
// First, update the asset duration if specified
@@ -151,11 +151,11 @@ const schedulePlaylistItem = {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
- Authorization: `Token ${bundle.authData.api_key}`
+ Authorization: `Token ${bundle.authData.api_key}`,
},
body: {
- duration: parseInt(bundle.inputData.duration, 10)
- }
+ duration: parseInt(bundle.inputData.duration, 10),
+ },
});
if (assetResponse.status >= 400) {
@@ -174,7 +174,7 @@ const schedulePlaylistItem = {
const payload = {
asset: bundle.inputData.asset_id,
- playlist: bundle.inputData.playlist_id
+ playlist: bundle.inputData.playlist_id,
};
// Only add conditions if they are specified
@@ -187,9 +187,9 @@ const schedulePlaylistItem = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
- Authorization: `Token ${bundle.authData.api_key}`
+ Authorization: `Token ${bundle.authData.api_key}`,
},
- body: payload
+ body: payload,
});
if (response.status >= 400) {
@@ -204,10 +204,10 @@ const schedulePlaylistItem = {
playlist: 1,
conditions: {
start_date: '2024-01-01T00:00:00Z',
- end_date: '2024-12-31T23:59:59Z'
- }
- }
- }
+ end_date: '2024-12-31T23:59:59Z',
+ },
+ },
+ },
};
// Assign Screen to Playlist Action
@@ -216,7 +216,7 @@ const assignScreenToPlaylist = {
noun: 'Screen Assignment',
display: {
label: 'Assign Screen to Playlist',
- description: 'Assign a screen to play a specific playlist'
+ description: 'Assign a screen to play a specific playlist',
},
operation: {
inputFields: [
@@ -226,7 +226,7 @@ const assignScreenToPlaylist = {
type: 'string',
required: true,
dynamic: 'get_screens.id.name',
- helpText: 'Select the screen to assign'
+ helpText: 'Select the screen to assign',
},
{
key: 'playlist_id',
@@ -234,8 +234,8 @@ const assignScreenToPlaylist = {
type: 'string',
required: true,
dynamic: 'get_playlists.id.name',
- helpText: 'Select the playlist to assign to the screen'
- }
+ helpText: 'Select the playlist to assign to the screen',
+ },
],
perform: async (z, bundle) => {
const response = await z.request({
@@ -243,11 +243,11 @@ const assignScreenToPlaylist = {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
- Authorization: `Token ${bundle.authData.api_key}`
+ Authorization: `Token ${bundle.authData.api_key}`,
},
body: {
- playlist: bundle.inputData.playlist_id
- }
+ playlist: bundle.inputData.playlist_id,
+ },
});
if (response.status >= 400) {
@@ -259,9 +259,9 @@ const assignScreenToPlaylist = {
sample: {
id: 1,
name: 'Sample Screen',
- playlist: 1
- }
- }
+ playlist: 1,
+ },
+ },
};
// Get Screens Trigger
@@ -270,15 +270,15 @@ const getScreens = {
noun: 'Screen',
display: {
label: 'Get Screens',
- description: 'Triggers when listing available Screenly screens.'
+ description: 'Triggers when listing available Screenly screens.',
},
operation: {
perform: async (z, bundle) => {
const response = await z.request({
url: 'https://api.screenlyapp.com/api/v4/screens/',
headers: {
- Authorization: `Token ${bundle.authData.api_key}`
- }
+ Authorization: `Token ${bundle.authData.api_key}`,
+ },
});
return utils.handleError(response, 'Failed to fetch screens');
@@ -287,9 +287,9 @@ const getScreens = {
id: 1,
name: 'Sample Screen',
description: 'A sample screen',
- playlist: 1
- }
- }
+ playlist: 1,
+ },
+ },
};
// Get Playlists Trigger
@@ -298,15 +298,15 @@ const getPlaylists = {
noun: 'Playlist',
display: {
label: 'Get Playlists',
- description: 'Triggers when listing available Screenly playlists.'
+ description: 'Triggers when listing available Screenly playlists.',
},
operation: {
perform: async (z, bundle) => {
const response = await z.request({
url: 'https://api.screenlyapp.com/api/v4/playlists/',
headers: {
- Authorization: `Token ${bundle.authData.api_key}`
- }
+ Authorization: `Token ${bundle.authData.api_key}`,
+ },
});
return utils.handleError(response, 'Failed to fetch playlists');
@@ -315,9 +315,9 @@ const getPlaylists = {
id: 1,
name: 'Sample Playlist',
description: 'A sample playlist',
- items_count: 5
- }
- }
+ items_count: 5,
+ },
+ },
};
// Get Assets Trigger
@@ -326,15 +326,15 @@ const getAssets = {
noun: 'Asset',
display: {
label: 'Get Assets',
- description: 'Triggers when listing available Screenly assets.'
+ description: 'Triggers when listing available Screenly assets.',
},
operation: {
perform: async (z, bundle) => {
const response = await z.request({
url: 'https://api.screenlyapp.com/api/v4/assets/',
headers: {
- Authorization: `Token ${bundle.authData.api_key}`
- }
+ Authorization: `Token ${bundle.authData.api_key}`,
+ },
});
return utils.handleError(response, 'Failed to fetch assets');
@@ -344,9 +344,9 @@ const getAssets = {
title: 'Sample Asset',
type: 'image',
duration: 10,
- url: 'https://example.com/sample.jpg'
- }
- }
+ url: 'https://example.com/sample.jpg',
+ },
+ },
};
// Helper to tag resources created by Zapier
@@ -358,7 +358,7 @@ const completeWorkflow = {
noun: 'Workflow',
display: {
label: 'Complete Workflow',
- description: 'Upload asset, create/select playlist, and assign to screen'
+ description: 'Upload asset, create/select playlist, and assign to screen',
},
operation: {
inputFields: [
@@ -367,14 +367,14 @@ const completeWorkflow = {
label: 'File URL',
type: 'string',
required: true,
- helpText: 'The URL of the file to upload'
+ helpText: 'The URL of the file to upload',
},
{
key: 'title',
label: 'Asset Title',
type: 'string',
required: true,
- helpText: 'Title of the asset'
+ helpText: 'Title of the asset',
},
{
key: 'duration',
@@ -382,7 +382,7 @@ const completeWorkflow = {
type: 'integer',
required: false,
default: '10',
- helpText: 'How long should the asset be shown (in seconds)'
+ helpText: 'How long should the asset be shown (in seconds)',
},
{
key: 'playlist_id',
@@ -390,14 +390,14 @@ const completeWorkflow = {
type: 'string',
required: false,
dynamic: 'get_playlists.id.name',
- helpText: 'Select an existing playlist or create a new one'
+ helpText: 'Select an existing playlist or create a new one',
},
{
key: 'new_playlist_name',
label: 'New Playlist Name',
type: 'string',
required: false,
- helpText: 'Name for the new playlist (if not using existing)'
+ helpText: 'Name for the new playlist (if not using existing)',
},
{
key: 'screen_id',
@@ -405,8 +405,8 @@ const completeWorkflow = {
type: 'string',
required: true,
dynamic: 'get_screens.id.name',
- helpText: 'Select the screen to assign'
- }
+ helpText: 'Select the screen to assign',
+ },
],
perform: async (z, bundle) => {
// Validate playlist information
@@ -418,11 +418,11 @@ const completeWorkflow = {
const formData = new FormData();
formData.append('title', bundle.inputData.title);
formData.append('duration', bundle.inputData.duration || 10);
- formData.append('tags', ZAPIER_TAG); // Tag the asset
+ formData.append('tags', ZAPIER_TAG); // Tag the asset
const fileResponse = await z.request({
url: bundle.inputData.file,
- raw: true
+ raw: true,
});
if (fileResponse.status >= 400) {
throw new Error(`Failed to fetch file: ${fileResponse.status}`);
@@ -434,9 +434,9 @@ const completeWorkflow = {
url: 'https://api.screenlyapp.com/api/v4/assets/',
method: 'POST',
headers: {
- Authorization: `Token ${bundle.authData.api_key}`
+ Authorization: `Token ${bundle.authData.api_key}`,
},
- body: formData
+ body: formData,
});
const asset = utils.handleError(assetResponse, 'Failed to upload asset');
@@ -451,12 +451,12 @@ const completeWorkflow = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
- Authorization: `Token ${bundle.authData.api_key}`
+ Authorization: `Token ${bundle.authData.api_key}`,
},
body: {
name: bundle.inputData.new_playlist_name,
- tags: [ZAPIER_TAG]
- }
+ tags: [ZAPIER_TAG],
+ },
});
const playlist = utils.handleError(playlistResponse, 'Failed to create playlist');
@@ -469,12 +469,12 @@ const completeWorkflow = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
- Authorization: `Token ${bundle.authData.api_key}`
+ Authorization: `Token ${bundle.authData.api_key}`,
},
body: {
asset: asset.id,
- playlist: playlistId
- }
+ playlist: playlistId,
+ },
});
utils.handleError(playlistItemResponse, 'Failed to add asset to playlist');
@@ -485,11 +485,11 @@ const completeWorkflow = {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
- Authorization: `Token ${bundle.authData.api_key}`
+ Authorization: `Token ${bundle.authData.api_key}`,
},
body: {
- playlist: playlistId
- }
+ playlist: playlistId,
+ },
});
utils.handleError(screenResponse, 'Failed to assign playlist to screen');
@@ -497,16 +497,16 @@ const completeWorkflow = {
return {
asset: asset,
playlist_id: playlistId,
- screen_id: bundle.inputData.screen_id
+ screen_id: bundle.inputData.screen_id,
};
},
sample: {
asset_id: 1,
playlist_id: 1,
screen_id: 1,
- message: 'Successfully set up complete workflow'
- }
- }
+ message: 'Successfully set up complete workflow',
+ },
+ },
};
// Cleanup Action
@@ -515,7 +515,7 @@ const cleanupZapierContent = {
noun: 'Cleanup',
display: {
label: 'Cleanup Zapier Content',
- description: 'Remove all content created by Zapier'
+ description: 'Remove all content created by Zapier',
},
operation: {
inputFields: [
@@ -524,8 +524,8 @@ const cleanupZapierContent = {
label: 'Confirm Cleanup',
type: 'boolean',
required: true,
- helpText: 'Are you sure you want to remove all content created by Zapier?'
- }
+ helpText: 'Are you sure you want to remove all content created by Zapier?',
+ },
],
perform: async (z, bundle) => {
if (!bundle.inputData.confirm) {
@@ -537,24 +537,26 @@ const cleanupZapierContent = {
url: 'https://api.screenlyapp.com/api/v4/assets/',
method: 'GET',
headers: {
- Authorization: `Token ${bundle.authData.api_key}`
- }
+ Authorization: `Token ${bundle.authData.api_key}`,
+ },
});
- const assets = utils.handleError(assetsResponse, 'Failed to fetch assets')
- .filter(asset => asset.tags.includes(ZAPIER_TAG));
+ const assets = utils
+ .handleError(assetsResponse, 'Failed to fetch assets')
+ .filter((asset) => asset.tags.includes(ZAPIER_TAG));
// Step 2: Get all Zapier-created playlists
const playlistsResponse = await z.request({
url: 'https://api.screenlyapp.com/api/v4/playlists/',
method: 'GET',
headers: {
- Authorization: `Token ${bundle.authData.api_key}`
- }
+ Authorization: `Token ${bundle.authData.api_key}`,
+ },
});
- const playlists = utils.handleError(playlistsResponse, 'Failed to fetch playlists')
- .filter(playlist => playlist.tags && playlist.tags.includes(ZAPIER_TAG));
+ const playlists = utils
+ .handleError(playlistsResponse, 'Failed to fetch playlists')
+ .filter((playlist) => playlist.tags && playlist.tags.includes(ZAPIER_TAG));
// Step 3: Delete assets
for (const asset of assets) {
@@ -562,8 +564,8 @@ const cleanupZapierContent = {
url: `https://api.screenlyapp.com/api/v4/assets/${asset.id}/`,
method: 'DELETE',
headers: {
- Authorization: `Token ${bundle.authData.api_key}`
- }
+ Authorization: `Token ${bundle.authData.api_key}`,
+ },
});
}
@@ -573,22 +575,22 @@ const cleanupZapierContent = {
url: `https://api.screenlyapp.com/api/v4/playlists/${playlist.id}/`,
method: 'DELETE',
headers: {
- Authorization: `Token ${bundle.authData.api_key}`
- }
+ Authorization: `Token ${bundle.authData.api_key}`,
+ },
});
}
return {
playlists_removed: playlists.length,
- assets_removed: assets.length
+ assets_removed: assets.length,
};
},
sample: {
assets_removed: 1,
playlists_removed: 1,
- message: 'Successfully cleaned up Zapier content'
- }
- }
+ message: 'Successfully cleaned up Zapier content',
+ },
+ },
};
// Export the app definition
@@ -601,7 +603,7 @@ module.exports = {
triggers: {
[getScreens.key]: getScreens,
[getPlaylists.key]: getPlaylists,
- [getAssets.key]: getAssets
+ [getAssets.key]: getAssets,
},
creates: {
@@ -609,10 +611,10 @@ module.exports = {
[schedulePlaylistItem.key]: schedulePlaylistItem,
[assignScreenToPlaylist.key]: assignScreenToPlaylist,
[completeWorkflow.key]: completeWorkflow,
- [cleanupZapierContent.key]: cleanupZapierContent
+ [cleanupZapierContent.key]: cleanupZapierContent,
},
searches: {},
- resources: {}
-};
\ No newline at end of file
+ resources: {},
+};
diff --git a/jest.config.js b/jest.config.js
index 7f84026..a6bd9a4 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -1,27 +1,20 @@
module.exports = {
- testEnvironment: "node",
+ testEnvironment: 'node',
testTimeout: 10000,
- testPathIgnorePatterns: [
- "/node_modules/",
- "\\.visual\\.test\\.js$"
- ],
- collectCoverageFrom: [
- "*.js",
- "!jest.config.js",
- "!jest.visual.config.js"
- ],
+ testPathIgnorePatterns: ['/node_modules/', '\\.visual\\.test\\.js$'],
+ collectCoverageFrom: ['*.js', '!jest.config.js', '!jest.visual.config.js'],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
- statements: 80
- }
+ statements: 80,
+ },
},
transform: {
- "^.+\\.js$": "babel-jest"
+ '^.+\\.js$': 'babel-jest',
},
transformIgnorePatterns: [
- "node_modules/(?!(node-fetch|fetch-blob|formdata-polyfill|data-uri-to-buffer|web-streams-polyfill)/)"
- ]
+ 'node_modules/(?!(node-fetch|fetch-blob|formdata-polyfill|data-uri-to-buffer|web-streams-polyfill)/)',
+ ],
};
diff --git a/jest.visual.config.js b/jest.visual.config.js
index 9180e13..0fbacb5 100644
--- a/jest.visual.config.js
+++ b/jest.visual.config.js
@@ -2,12 +2,12 @@ module.exports = {
testEnvironment: 'node',
testMatch: ['**/test/*.visual.test.js'],
transform: {
- '^.+\\.jsx?$': 'babel-jest'
+ '^.+\\.jsx?$': 'babel-jest',
},
setupFilesAfterEnv: ['./test/setup.visual.js'],
testTimeout: 30000,
moduleNameMapper: {
- '^(\\.{1,2}/.*)\\.js$': '$1'
+ '^(\\.{1,2}/.*)\\.js$': '$1',
},
- testRunner: 'jest-circus/runner'
-};
\ No newline at end of file
+ testRunner: 'jest-circus/runner',
+};
diff --git a/test/index.test.js b/test/index.test.js
index a4819f0..f12b060 100644
--- a/test/index.test.js
+++ b/test/index.test.js
@@ -20,8 +20,8 @@ describe('Authentication', () => {
test('succeeds with valid API key', async () => {
const bundle = {
authData: {
- api_key: TEST_API_KEY
- }
+ api_key: TEST_API_KEY,
+ },
};
nock('https://api.screenlyapp.com')
@@ -36,8 +36,8 @@ describe('Authentication', () => {
test('fails with invalid API key', async () => {
const bundle = {
authData: {
- api_key: 'invalid-api-key'
- }
+ api_key: 'invalid-api-key',
+ },
};
nock('https://api.screenlyapp.com')
@@ -45,8 +45,7 @@ describe('Authentication', () => {
.matchHeader('Authorization', 'Token invalid-api-key')
.reply(401, { detail: 'Invalid token' });
- await expect(appTester(App.authentication.test, bundle))
- .rejects.toThrow();
+ await expect(appTester(App.authentication.test, bundle)).rejects.toThrow();
});
});
@@ -58,19 +57,17 @@ describe('Upload Asset', () => {
test('successfully uploads an asset', async () => {
const bundle = {
authData: {
- api_key: TEST_API_KEY
+ api_key: TEST_API_KEY,
},
inputData: {
file: 'https://example.com/image.jpg',
title: 'Test Image',
- duration: 10
- }
+ duration: 10,
+ },
};
// Mock the file download
- nock('https://example.com')
- .get('/image.jpg')
- .reply(200, 'fake-file-content');
+ nock('https://example.com').get('/image.jpg').reply(200, 'fake-file-content');
// Mock the asset upload
nock('https://api.screenlyapp.com')
@@ -79,7 +76,7 @@ describe('Upload Asset', () => {
.reply(201, {
id: 'asset-123',
title: 'Test Image',
- duration: 10
+ duration: 10,
});
const response = await appTester(App.creates.upload_asset.operation.perform, bundle);
@@ -91,21 +88,18 @@ describe('Upload Asset', () => {
test('handles upload failure', async () => {
const bundle = {
authData: {
- api_key: TEST_API_KEY
+ api_key: TEST_API_KEY,
},
inputData: {
file: 'https://example.com/image.jpg',
title: 'Test Image',
- duration: 10
- }
+ duration: 10,
+ },
};
- nock('https://example.com')
- .get('/image.jpg')
- .reply(404);
+ nock('https://example.com').get('/image.jpg').reply(404);
- await expect(appTester(App.creates.upload_asset.operation.perform, bundle))
- .rejects.toThrow();
+ await expect(appTester(App.creates.upload_asset.operation.perform, bundle)).rejects.toThrow();
});
});
@@ -117,37 +111,37 @@ describe('Schedule Playlist Item', () => {
test('successfully adds asset to playlist without conditions', async () => {
const bundle = {
authData: {
- api_key: TEST_API_KEY
+ api_key: TEST_API_KEY,
},
inputData: {
playlist_id: 'playlist-123',
asset_id: 'asset-123',
- duration: 15
- }
+ duration: 15,
+ },
};
// Mock the asset duration update
nock('https://api.screenlyapp.com')
.patch('/api/v4/assets/asset-123/', {
- duration: 15
+ duration: 15,
})
.matchHeader('Authorization', `Token ${TEST_API_KEY}`)
.reply(200, {
id: 'asset-123',
- duration: 15
+ duration: 15,
});
// Mock the playlist item creation
nock('https://api.screenlyapp.com')
.post('/api/v4/playlist-items/', {
asset: 'asset-123',
- playlist: 'playlist-123'
+ playlist: 'playlist-123',
})
.matchHeader('Authorization', `Token ${TEST_API_KEY}`)
.reply(201, {
id: 'item-123',
playlist: 'playlist-123',
- asset: 'asset-123'
+ asset: 'asset-123',
});
const response = await appTester(App.creates.schedule_playlist_item.operation.perform, bundle);
@@ -159,26 +153,26 @@ describe('Schedule Playlist Item', () => {
test('successfully adds asset with scheduling', async () => {
const bundle = {
authData: {
- api_key: TEST_API_KEY
+ api_key: TEST_API_KEY,
},
inputData: {
playlist_id: 'playlist-123',
asset_id: 'asset-123',
duration: 20,
start_date: '2023-12-01T00:00:00Z',
- end_date: '2023-12-31T23:59:59Z'
- }
+ end_date: '2023-12-31T23:59:59Z',
+ },
};
// Mock the asset duration update
nock('https://api.screenlyapp.com')
.patch('/api/v4/assets/asset-123/', {
- duration: 20
+ duration: 20,
})
.matchHeader('Authorization', `Token ${TEST_API_KEY}`)
.reply(200, {
id: 'asset-123',
- duration: 20
+ duration: 20,
});
// Mock the playlist item creation
@@ -188,8 +182,8 @@ describe('Schedule Playlist Item', () => {
playlist: 'playlist-123',
conditions: {
start_date: '2023-12-01T00:00:00Z',
- end_date: '2023-12-31T23:59:59Z'
- }
+ end_date: '2023-12-31T23:59:59Z',
+ },
})
.matchHeader('Authorization', `Token ${TEST_API_KEY}`)
.reply(201, {
@@ -198,8 +192,8 @@ describe('Schedule Playlist Item', () => {
asset: 'asset-123',
conditions: {
start_date: '2023-12-01T00:00:00Z',
- end_date: '2023-12-31T23:59:59Z'
- }
+ end_date: '2023-12-31T23:59:59Z',
+ },
});
const response = await appTester(App.creates.schedule_playlist_item.operation.perform, bundle);
@@ -214,19 +208,20 @@ describe('Schedule Playlist Item', () => {
inputData: {
asset_id: 'asset-123',
playlist_id: 'playlist-123',
- duration: -1
- }
+ duration: -1,
+ },
};
nock('https://api.screenlyapp.com')
.patch('/api/v4/assets/asset-123/', {
- duration: -1
+ duration: -1,
})
.matchHeader('Authorization', `Token ${TEST_API_KEY}`)
.reply(400, { detail: 'Invalid duration' });
- await expect(appTester(App.creates.schedule_playlist_item.operation.perform, bundle))
- .rejects.toThrow();
+ await expect(
+ appTester(App.creates.schedule_playlist_item.operation.perform, bundle)
+ ).rejects.toThrow();
});
test('handles playlist addition failure', async () => {
@@ -234,20 +229,21 @@ describe('Schedule Playlist Item', () => {
authData: { api_key: TEST_API_KEY },
inputData: {
asset_id: 'asset-123',
- playlist_id: 'playlist-123'
- }
+ playlist_id: 'playlist-123',
+ },
};
nock('https://api.screenlyapp.com')
.post('/api/v4/playlist-items/', {
asset: 'asset-123',
- playlist: 'playlist-123'
+ playlist: 'playlist-123',
})
.matchHeader('Authorization', `Token ${TEST_API_KEY}`)
.reply(404, { detail: 'Playlist not found' });
- await expect(appTester(App.creates.schedule_playlist_item.operation.perform, bundle))
- .rejects.toThrow();
+ await expect(
+ appTester(App.creates.schedule_playlist_item.operation.perform, bundle)
+ ).rejects.toThrow();
});
});
@@ -256,21 +252,22 @@ describe('Helper Functions', () => {
const z = {
request: jest.fn().mockResolvedValue({
status: 404,
- json: { error: 'Not found' }
+ json: { error: 'Not found' },
}),
authData: {
- api_key: TEST_API_KEY
- }
+ api_key: TEST_API_KEY,
+ },
};
- await expect(utils.makeRequest(z, 'https://api.screenlyapp.com/api/v4/test/'))
- .rejects.toThrow('Screenly API Error');
+ await expect(utils.makeRequest(z, 'https://api.screenlyapp.com/api/v4/test/')).rejects.toThrow(
+ 'Screenly API Error'
+ );
});
test('handleError returns json on success', async () => {
const response = {
status: 200,
- json: { data: 'test' }
+ json: { data: 'test' },
};
const result = utils.handleError(response, 'Error message');
@@ -281,25 +278,25 @@ describe('Helper Functions', () => {
const z = {
request: jest.fn().mockResolvedValue({
status: 200,
- json: { data: 'test' }
+ json: { data: 'test' },
}),
authData: {
- api_key: TEST_API_KEY
- }
+ api_key: TEST_API_KEY,
+ },
};
await utils.makeRequest(z, 'https://api.screenlyapp.com/api/v4/test/', {
headers: {
- 'Content-Type': 'application/json'
- }
+ 'Content-Type': 'application/json',
+ },
});
expect(z.request).toHaveBeenCalledWith({
url: 'https://api.screenlyapp.com/api/v4/test/',
headers: {
'Content-Type': 'application/json',
- Authorization: `Token ${TEST_API_KEY}`
- }
+ Authorization: `Token ${TEST_API_KEY}`,
+ },
});
});
});
@@ -312,8 +309,8 @@ describe('Dynamic Dropdowns', () => {
test('fetches playlists for dropdown', async () => {
const bundle = {
authData: {
- api_key: TEST_API_KEY
- }
+ api_key: TEST_API_KEY,
+ },
};
nock('https://api.screenlyapp.com')
@@ -321,7 +318,7 @@ describe('Dynamic Dropdowns', () => {
.matchHeader('Authorization', `Token ${TEST_API_KEY}`)
.reply(200, [
{ id: 'playlist-1', name: 'Playlist 1' },
- { id: 'playlist-2', name: 'Playlist 2' }
+ { id: 'playlist-2', name: 'Playlist 2' },
]);
const response = await appTester(App.triggers.get_playlists.operation.perform, bundle);
@@ -333,8 +330,8 @@ describe('Dynamic Dropdowns', () => {
test('fetches assets for dropdown', async () => {
const bundle = {
authData: {
- api_key: TEST_API_KEY
- }
+ api_key: TEST_API_KEY,
+ },
};
nock('https://api.screenlyapp.com')
@@ -342,7 +339,7 @@ describe('Dynamic Dropdowns', () => {
.matchHeader('Authorization', `Token ${TEST_API_KEY}`)
.reply(200, [
{ id: 'asset-1', title: 'Asset 1' },
- { id: 'asset-2', title: 'Asset 2' }
+ { id: 'asset-2', title: 'Asset 2' },
]);
const response = await appTester(App.triggers.get_assets.operation.perform, bundle);
@@ -354,8 +351,8 @@ describe('Dynamic Dropdowns', () => {
test('fetches screens for dropdown', async () => {
const bundle = {
authData: {
- api_key: TEST_API_KEY
- }
+ api_key: TEST_API_KEY,
+ },
};
nock('https://api.screenlyapp.com')
@@ -363,7 +360,7 @@ describe('Dynamic Dropdowns', () => {
.matchHeader('Authorization', `Token ${TEST_API_KEY}`)
.reply(200, [
{ id: 'screen-1', name: 'Screen 1' },
- { id: 'screen-2', name: 'Screen 2' }
+ { id: 'screen-2', name: 'Screen 2' },
]);
const response = await appTester(App.triggers.get_screens.operation.perform, bundle);
@@ -385,37 +382,33 @@ describe('Complete Workflow', () => {
file: 'https://example.com/test.jpg',
title: 'Test Asset',
playlist_id: 'playlist-123',
- screen_id: 'screen-123'
- }
+ screen_id: 'screen-123',
+ },
};
- nock('https://example.com')
- .get('/test.jpg')
- .reply(200, Buffer.from('fake-image-data'));
+ nock('https://example.com').get('/test.jpg').reply(200, Buffer.from('fake-image-data'));
- nock('https://api.screenlyapp.com')
- .post('/api/v4/assets/')
- .reply(201, {
- id: 'asset-123',
- title: 'Test Asset'
- });
+ nock('https://api.screenlyapp.com').post('/api/v4/assets/').reply(201, {
+ id: 'asset-123',
+ title: 'Test Asset',
+ });
nock('https://api.screenlyapp.com')
.post('/api/v4/playlist-items/', {
asset: 'asset-123',
- playlist: 'playlist-123'
+ playlist: 'playlist-123',
})
.reply(201, {
- id: 'item-123'
+ id: 'item-123',
});
nock('https://api.screenlyapp.com')
.patch('/api/v4/screens/screen-123/', {
- playlist: 'playlist-123'
+ playlist: 'playlist-123',
})
.reply(200, {
id: 'screen-123',
- playlist: 'playlist-123'
+ playlist: 'playlist-123',
});
const response = await appTester(App.creates.complete_workflow.operation.perform, bundle);
@@ -431,48 +424,44 @@ describe('Complete Workflow', () => {
file: 'https://example.com/test.jpg',
title: 'Test Asset',
new_playlist_name: 'New Playlist',
- screen_id: 'screen-123'
- }
+ screen_id: 'screen-123',
+ },
};
- nock('https://example.com')
- .get('/test.jpg')
- .reply(200, Buffer.from('fake-image-data'));
+ nock('https://example.com').get('/test.jpg').reply(200, Buffer.from('fake-image-data'));
- nock('https://api.screenlyapp.com')
- .post('/api/v4/assets/')
- .reply(201, {
- id: 'asset-123',
- title: 'Test Asset'
- });
+ nock('https://api.screenlyapp.com').post('/api/v4/assets/').reply(201, {
+ id: 'asset-123',
+ title: 'Test Asset',
+ });
nock('https://api.screenlyapp.com')
.post('/api/v4/playlists/', {
name: 'New Playlist',
- tags: ['created_by_zapier']
+ tags: ['created_by_zapier'],
})
.reply(201, {
id: 'playlist-123',
name: 'New Playlist',
- tags: ['created_by_zapier']
+ tags: ['created_by_zapier'],
});
nock('https://api.screenlyapp.com')
.post('/api/v4/playlist-items/', {
asset: 'asset-123',
- playlist: 'playlist-123'
+ playlist: 'playlist-123',
})
.reply(201, {
- id: 'item-123'
+ id: 'item-123',
});
nock('https://api.screenlyapp.com')
.patch('/api/v4/screens/screen-123/', {
- playlist: 'playlist-123'
+ playlist: 'playlist-123',
})
.reply(200, {
id: 'screen-123',
- playlist: 'playlist-123'
+ playlist: 'playlist-123',
});
const response = await appTester(App.creates.complete_workflow.operation.perform, bundle);
@@ -487,12 +476,13 @@ describe('Complete Workflow', () => {
inputData: {
file: 'https://example.com/test.jpg',
title: 'Test Asset',
- screen_id: 'screen-123'
- }
+ screen_id: 'screen-123',
+ },
};
- await expect(appTester(App.creates.complete_workflow.operation.perform, bundle))
- .rejects.toThrow('Either select an existing playlist or provide a name for a new one');
+ await expect(
+ appTester(App.creates.complete_workflow.operation.perform, bundle)
+ ).rejects.toThrow('Either select an existing playlist or provide a name for a new one');
});
});
@@ -504,11 +494,11 @@ describe('Cleanup', () => {
test('successfully cleans up Zapier content', async () => {
const bundle = {
authData: {
- api_key: TEST_API_KEY
+ api_key: TEST_API_KEY,
},
inputData: {
- confirm: true
- }
+ confirm: true,
+ },
};
// Mock assets list with some Zapier-created assets
@@ -518,7 +508,7 @@ describe('Cleanup', () => {
.reply(200, [
{ id: 'asset-1', title: 'Asset 1', tags: ['created_by_zapier'] },
{ id: 'asset-2', title: 'Asset 2', tags: [] },
- { id: 'asset-3', title: 'Asset 3', tags: ['created_by_zapier'] }
+ { id: 'asset-3', title: 'Asset 3', tags: ['created_by_zapier'] },
]);
// Mock playlists list with some Zapier-created playlists
@@ -527,7 +517,7 @@ describe('Cleanup', () => {
.matchHeader('Authorization', `Token ${TEST_API_KEY}`)
.reply(200, [
{ id: 'playlist-1', name: 'Playlist 1', tags: ['created_by_zapier'] },
- { id: 'playlist-2', name: 'Playlist 2', tags: [] }
+ { id: 'playlist-2', name: 'Playlist 2', tags: [] },
]);
// Mock playlist deletions
@@ -556,22 +546,23 @@ describe('Cleanup', () => {
const bundle = {
authData: { api_key: TEST_API_KEY },
inputData: {
- confirm: false
- }
+ confirm: false,
+ },
};
- await expect(appTester(App.creates.cleanup_zapier_content.operation.perform, bundle))
- .rejects.toThrow('Please confirm the cleanup operation');
+ await expect(
+ appTester(App.creates.cleanup_zapier_content.operation.perform, bundle)
+ ).rejects.toThrow('Please confirm the cleanup operation');
});
test('handles empty lists', async () => {
const bundle = {
authData: {
- api_key: TEST_API_KEY
+ api_key: TEST_API_KEY,
},
inputData: {
- confirm: true
- }
+ confirm: true,
+ },
};
// Mock empty assets list
@@ -590,4 +581,4 @@ describe('Cleanup', () => {
expect(response.playlists_removed).toBe(0);
expect(response.assets_removed).toBe(0);
});
-});
\ No newline at end of file
+});
diff --git a/test/index.visual.test.js b/test/index.visual.test.js
index 54e63c8..edb7b44 100644
--- a/test/index.visual.test.js
+++ b/test/index.visual.test.js
@@ -22,14 +22,14 @@ describeVisual('Zapier Visual Tests', () => {
'--disable-gpu',
'--window-size=1280,800',
'--disable-web-security',
- '--disable-features=IsolateOrigins,site-per-process'
+ '--disable-features=IsolateOrigins,site-per-process',
],
ignoreHTTPSErrors: true,
defaultViewport: {
width: 1280,
height: 800,
- deviceScaleFactor: 1
- }
+ deviceScaleFactor: 1,
+ },
});
});
@@ -99,7 +99,7 @@ describeVisual('Zapier Visual Tests', () => {
type: 'png',
encoding: null,
omitBackground: true,
- fullPage: true
+ fullPage: true,
});
expect(Buffer.from(screenshot)).toMatchImageSnapshot();
});
@@ -165,7 +165,7 @@ describeVisual('Zapier Visual Tests', () => {
type: 'png',
encoding: null,
omitBackground: true,
- fullPage: true
+ fullPage: true,
});
expect(Buffer.from(screenshot)).toMatchImageSnapshot();
});
@@ -203,8 +203,8 @@ describeVisual('Zapier Visual Tests', () => {
type: 'png',
encoding: null,
omitBackground: true,
- fullPage: true
+ fullPage: true,
});
expect(Buffer.from(screenshot)).toMatchImageSnapshot();
});
-});
\ No newline at end of file
+});
diff --git a/test/setup.visual.js b/test/setup.visual.js
index 56ff5ee..30ed332 100644
--- a/test/setup.visual.js
+++ b/test/setup.visual.js
@@ -16,10 +16,10 @@ const toMatchImageSnapshot = configureToMatchImageSnapshot({
// Better error handling
updatePassedSnapshot: false,
blur: 2,
- allowSizeMismatch: true
+ allowSizeMismatch: true,
});
expect.extend({ toMatchImageSnapshot });
// Increase Jest timeout for visual tests
-jest.setTimeout(30000);
\ No newline at end of file
+jest.setTimeout(30000);
diff --git a/utils.js b/utils.js
index 9f88577..b46b9f3 100644
--- a/utils.js
+++ b/utils.js
@@ -13,8 +13,8 @@ const makeRequest = async (z, url, options = {}) => {
...options,
headers: {
...options.headers,
- Authorization: `Token ${z.authData.api_key}`
- }
+ Authorization: `Token ${z.authData.api_key}`,
+ },
});
return handleError(response, 'Screenly API Error');
@@ -22,5 +22,5 @@ const makeRequest = async (z, url, options = {}) => {
module.exports = {
handleError,
- makeRequest
-};
\ No newline at end of file
+ makeRequest,
+};