Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ The Deploy Course Action is a composite GitHub Action that automates the deploym
| `course-json-path` | string | `course.json` | Path to course configuration file |
| `article-type` | string | `course` | Type of article (`course` or `blog`) |
| `force-duplicate-questions` | string | `false` | Force upload duplicate questions (`true` or `false`) |
| `certificate-enabled` | string | `false` | Enable certificates for this course (`true` or `false`) |
| `certificate-criteria-type` | string | `completion` | Certificate criteria type: `completion` or `points` |
| `certificate-criteria-value` | string | `100` | Criteria value (0-100 for completion, any number for points) |
| `draft` | string | `false` | Create draft course (`true` or `false`) |

### Outputs
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ uses: qBraid/upload-course-action@v0.1.0-beta
- PR changelog guard workflow that requires `CHANGELOG.md` updates when code-related files change
- Typos configuration file to ignore generated artifacts and allow project-specific terms (`qbraid`, `qbook`)

### Added
- Certificate settings support for course creation and updates via `certificate-enabled`, `certificate-criteria-type`, and `certificate-criteria-value` action inputs
- Certificate criteria validation (type must be `completion` or `points`; value must be in range 0–100 for completion)
- Certificate settings payload construction in `deploy_common.py` shared by create and update flows
- Comprehensive unit tests for certificate settings handling in `CourseDeployer` and `CourseUpdater`

### Changed
- Increased default polling attempts to 20 (from 10)
- Updated CI coverage upload to Codecov to support token upload with OIDC fallback when `CODECOV_TOKEN` is unavailable
Expand Down
50 changes: 50 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ jobs:
| `course-json-path` | Path to `course.json` file | No | `course.json` |
| `article-type` | Type of article to create (`course` or `blog`) | No | `course` |
| `force-duplicate-questions` | Whether to force upload duplicate questions (`true` or `false`) | No | `false` |
| `certificate-enabled` | Enable certificates for this course (`true` or `false`) | No | `false` |
| `certificate-criteria-type` | Certificate criteria type: `completion` or `points` | No | `completion` |
| `certificate-criteria-value` | Criteria value (0-100 for completion, any number for points) | No | `100` |
| `max-poll-attempts` | Maximum polling attempts before timing out | No | `20` |
| `poll-interval-seconds` | Seconds to wait between polling attempts | No | `15` |
| `max-consecutive-errors` | Maximum consecutive polling errors before failing | No | `5` |
Expand All @@ -80,6 +83,53 @@ jobs:
| `course-custom-id` | Custom ID of the deployed course |
| `qbook_url` | URL of the deployed course |

## Certificate Settings

Configure certificate issuance for your course using the certificate input fields. If `certificate-enabled` is not set or is `false`, certificates are disabled.

**Note:** Certificate settings only apply when `article-type` is `course`. They are ignored for `blog` articles.

### Inputs

| Input | Description | Default |
| :--- | :--- | :--- |
| `certificate-enabled` | Enable certificates for the course | `false` |
| `certificate-criteria-type` | `completion` (percentage-based) or `points` (score-based) | `completion` |
| `certificate-criteria-value` | Required value (0-100 for completion, any number for points) | `100` |

### Examples

**Completion-based certificate (require 80% completion):**
```yaml
- uses: qBraid/upload-course-action@v0.1.0-beta
with:
api-key: ${{ secrets.QBRAID_API_KEY }}
repo-read-token: ${{ secrets.GITHUB_TOKEN }}
certificate-enabled: 'true'
certificate-criteria-type: 'completion'
certificate-criteria-value: '80'
```

**Points-based certificate (require 500 points):**
```yaml
- uses: qBraid/upload-course-action@v0.1.0-beta
with:
api-key: ${{ secrets.QBRAID_API_KEY }}
repo-read-token: ${{ secrets.GITHUB_TOKEN }}
certificate-enabled: 'true'
certificate-criteria-type: 'points'
certificate-criteria-value: '500'
```

**Certificates disabled (default):**
```yaml
- uses: qBraid/upload-course-action@v0.1.0-beta
with:
api-key: ${{ secrets.QBRAID_API_KEY }}
repo-read-token: ${{ secrets.GITHUB_TOKEN }}
# certificate-enabled defaults to 'false'
```

## How it Works

This action validates your course structure and creates it via the qBraid API:
Expand Down
22 changes: 20 additions & 2 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@ inputs:
description: 'Whether to force duplicate questions'
required: false
default: 'false'
certificate-enabled:
description: 'Enable certificates for this course (true or false)'
required: false
default: 'false'
certificate-criteria-type:
description: 'Certificate criteria type: completion or points'
required: false
default: 'completion'
certificate-criteria-value:
description: 'Certificate criteria value (0-100 for completion, any number for points)'
required: false
default: '100'
Comment thread
This conversation was marked as resolved.
draft:
description: 'Whether to create a draft course (true or false)'
required: false
Expand Down Expand Up @@ -116,7 +128,10 @@ runs:
--repo-url '${{ github.server_url }}/${{ github.repository }}' \
--commit-sha '${{ github.sha }}' \
--article-type '${{ inputs.article-type }}' \
--force-duplicate-questions '${{ inputs.force-duplicate-questions }}'
--force-duplicate-questions '${{ inputs.force-duplicate-questions }}' \
--certificate-enabled '${{ inputs.certificate-enabled }}' \
--certificate-criteria-type '${{ inputs.certificate-criteria-type }}' \
--certificate-criteria-value '${{ inputs.certificate-criteria-value }}'

- name: Stage 5b - Update Course
if: inputs.mode == 'update'
Expand All @@ -130,7 +145,10 @@ runs:
--repo-url '${{ github.server_url }}/${{ github.repository }}' \
--commit-sha '${{ github.sha }}' \
--article-type '${{ inputs.article-type }}' \
--force-duplicate-questions '${{ inputs.force-duplicate-questions }}'
--force-duplicate-questions '${{ inputs.force-duplicate-questions }}' \
--certificate-enabled '${{ inputs.certificate-enabled }}' \
--certificate-criteria-type '${{ inputs.certificate-criteria-type }}' \
--certificate-criteria-value '${{ inputs.certificate-criteria-value }}'

- name: Stage 6 - Poll status end point for completion
id: poll-status
Expand Down
9 changes: 9 additions & 0 deletions examples/example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@ jobs:
# Optional: Whether to force upload duplicate questions (defaults to 'false')
force-duplicate-questions: 'false'

# Optional: Enable certificates for this course (defaults to 'false')
certificate-enabled: 'false'

# Optional: Certificate criteria type - 'completion' or 'points' (defaults to 'completion')
certificate-criteria-type: 'completion'

# Optional: Certificate criteria value (0-100 for completion, any number for points)
certificate-criteria-value: '100'

# Optional: Operation mode ('create' or 'update', defaults to 'create')
# Can be dynamic based on workflow inputs or variables
mode: ${{ inputs.mode || 'create' }}
Expand Down
9 changes: 9 additions & 0 deletions examples/workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ jobs:
# Optional: Whether to force upload duplicate questions (defaults to 'false')
force-duplicate-questions: 'false'

# Optional: Enable certificates for this course (defaults to 'false')
certificate-enabled: 'false'

# Optional: Certificate criteria type - 'completion' or 'points' (defaults to 'completion')
certificate-criteria-type: 'completion'

# Optional: Certificate criteria value (0-100 for completion, any number for points)
certificate-criteria-value: '100'

- name: Show deployment results
if: success()
run: |
Expand Down
1 change: 0 additions & 1 deletion src/scripts/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ class Config:

# API Configuration
DEFAULT_API_BASE_URL: str = "https://api-v2.qbraid.com/api/v1"
# DEFAULT_API_BASE_URL: str = "https://ad78f4d43151.ngrok-free.app/app1/api/v1"
API_BASE_URL: str = os.getenv("QBRAID_API_BASE_URL", DEFAULT_API_BASE_URL)
REQUEST_TIMEOUT_SECONDS: int = _get_env_int("QBRAID_REQUEST_TIMEOUT_SECONDS", 30)
MAX_POLL_ATTEMPTS: int = _get_env_int("QBRAID_MAX_POLL_ATTEMPTS", 20)
Expand Down
46 changes: 40 additions & 6 deletions src/scripts/create_course.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@

import argparse
import sys
from typing import Any, Dict
from typing import Any, Dict, Optional

from common import ActionError, ArticleType, Config, ValidationError, setup_logging
from deploy_common import (
CourseDeployer,
build_certificate_settings,
validate_api_key,
validate_article_type,
validate_boolean,
validate_certificate_criteria_type,
validate_certificate_criteria_value,
validate_commit_sha,
validate_repo_token,
validate_repo_url,
Expand All @@ -29,23 +32,31 @@ def __init__(
commit_sha: str,
article_type: str = "course",
force_duplicate_questions: bool = True,
certificate_settings: Optional[Dict[str, Any]] = None,
):
super().__init__(
api_key, repo_read_token, repo_url, commit_sha, force_duplicate_questions
api_key,
repo_read_token,
repo_url,
commit_sha,
article_type,
force_duplicate_questions,
certificate_settings,
)
try:
self.article_type = ArticleType(article_type)
self._article_type_enum = ArticleType(article_type)
except ValueError:
logger.warning(
f"Invalid article type '{article_type}'. Defaulting to 'course'."
)
self.article_type = ArticleType.COURSE
self._article_type_enum = ArticleType.COURSE
self.article_type = "course"

def run(self) -> None:
"""Creates a course on qBraid using the API."""
logger.info(f"Creating article of type: {self.article_type.value}")
logger.info(f"Creating article of type: {self._article_type_enum.value}")

url = f"/learn/articles/{self.article_type.value}/ingest"
url = f"/learn/articles/{self._article_type_enum.value}/ingest"
headers = {"X-API-Key": self.api_key, "Content-Type": "application/json"}
payload = self.get_common_payload()

Expand All @@ -67,6 +78,7 @@ def create_course(
commit_sha: str,
article_type: str = "course",
force_duplicate_questions: bool = True,
certificate_settings: Optional[Dict[str, Any]] = None,
):
"""Wrapper for execution."""
creator = CourseCreator(
Expand All @@ -76,6 +88,7 @@ def create_course(
commit_sha,
article_type,
force_duplicate_questions,
certificate_settings,
)
try:
creator.run()
Expand All @@ -95,16 +108,37 @@ def create_course(
parser.add_argument(
"--force-duplicate-questions", required=True, type=validate_boolean
)
parser.add_argument(
"--certificate-enabled", required=False, type=validate_boolean, default=False
)
parser.add_argument(
"--certificate-criteria-type",
required=False,
type=validate_certificate_criteria_type,
default="completion",
)
parser.add_argument(
"--certificate-criteria-value",
required=False,
type=validate_certificate_criteria_value,
default=None,
)

try:
args = parser.parse_args()
certificate_settings = build_certificate_settings(
args.certificate_enabled,
args.certificate_criteria_type,
args.certificate_criteria_value,
)
create_course(
args.api_key,
args.repo_read_token,
args.repo_url,
args.commit_sha,
args.article_type,
args.force_duplicate_questions,
certificate_settings,
)
except (ValidationError, argparse.ArgumentError) as e:
logger.error(f"Validation error: {e}")
Expand Down
Loading
Loading